mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge branch 'master' into all_options
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# [](https://xtermjs.org)
|
||||
|
||||
[](https://travis-ci.org/xtermjs/xterm.js) [](https://coveralls.io/github/xtermjs/xterm.js?branch=master) [](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://www.jsdelivr.com/package/npm/xterm)
|
||||
[](https://travis-ci.org/xtermjs/xterm.js) [](https://xtermjs.visualstudio.com/xterm.js/_build/index?definitionId=1) [](https://coveralls.io/github/xtermjs/xterm.js?branch=master) [](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://www.jsdelivr.com/package/npm/xterm)
|
||||
|
||||
Xterm.js is a terminal front-end component written in JavaScript that works in the browser.
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/chai": "^3.4.34",
|
||||
"@types/glob": "^5.0.35",
|
||||
"@types/jsdom": "^11.0.1",
|
||||
"@types/mocha": "^2.2.33",
|
||||
"@types/node": "6.0.108",
|
||||
|
||||
@@ -48,6 +48,63 @@ describe('Buffer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWrappedRangeForLine', () => {
|
||||
describe('non-wrapped', () => {
|
||||
it('should return a single row for the first row', () => {
|
||||
buffer.fillViewportRows();
|
||||
assert.deepEqual(buffer.getWrappedRangeForLine(0), { first: 0, last: 0 });
|
||||
});
|
||||
it('should return a single row for a middle row', () => {
|
||||
buffer.fillViewportRows();
|
||||
assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 12, last: 12 });
|
||||
});
|
||||
it('should return a single row for the last row', () => {
|
||||
buffer.fillViewportRows();
|
||||
assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 1), { first: 23, last: 23 });
|
||||
});
|
||||
});
|
||||
describe('wrapped', () => {
|
||||
it('should return a range for the first row', () => {
|
||||
buffer.fillViewportRows();
|
||||
(<any> buffer.lines.get(1)).isWrapped = true;
|
||||
assert.deepEqual(buffer.getWrappedRangeForLine(0), { first: 0, last: 1 });
|
||||
});
|
||||
it('should return a range for a middle row wrapping upwards', () => {
|
||||
buffer.fillViewportRows();
|
||||
(<any> buffer.lines.get(12)).isWrapped = true;
|
||||
assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 11, last: 12 });
|
||||
});
|
||||
it('should return a range for a middle row wrapping downwards', () => {
|
||||
buffer.fillViewportRows();
|
||||
(<any> buffer.lines.get(13)).isWrapped = true;
|
||||
assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 12, last: 13 });
|
||||
});
|
||||
it('should return a range for a middle row wrapping both ways', () => {
|
||||
buffer.fillViewportRows();
|
||||
(<any> buffer.lines.get(11)).isWrapped = true;
|
||||
(<any> buffer.lines.get(12)).isWrapped = true;
|
||||
(<any> buffer.lines.get(13)).isWrapped = true;
|
||||
(<any> buffer.lines.get(14)).isWrapped = true;
|
||||
assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 10, last: 14 });
|
||||
});
|
||||
it('should return a range for the last row', () => {
|
||||
buffer.fillViewportRows();
|
||||
(<any> buffer.lines.get(23)).isWrapped = true;
|
||||
assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 1), { first: 22, last: 23 });
|
||||
});
|
||||
it('should return a range for a row that wraps upward to first row', () => {
|
||||
buffer.fillViewportRows();
|
||||
(<any> buffer.lines.get(1)).isWrapped = true;
|
||||
assert.deepEqual(buffer.getWrappedRangeForLine(1), { first: 0, last: 1 });
|
||||
});
|
||||
it('should return a range for a row that wraps downward to last row', () => {
|
||||
buffer.fillViewportRows();
|
||||
(<any> buffer.lines.get(buffer.lines.length - 1)).isWrapped = true;
|
||||
assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 2), { first: 22, last: 23 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resize', () => {
|
||||
describe('column size is reduced', () => {
|
||||
it('should not trim the data in the buffer', () => {
|
||||
|
||||
+16
-1
@@ -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);
|
||||
@@ -259,6 +260,20 @@ export class Buffer implements IBuffer {
|
||||
return lineString.substring(startIndex, endIndex);
|
||||
}
|
||||
|
||||
public getWrappedRangeForLine(y: number): { first: number, last: number } {
|
||||
let first = y;
|
||||
let last = y;
|
||||
// Scan upwards for wrapped lines
|
||||
while (first > 0 && (<any>this.lines.get(first)).isWrapped) {
|
||||
first--;
|
||||
}
|
||||
// Scan downwards for wrapped lines
|
||||
while (last + 1 < this.lines.length && (<any>this.lines.get(last + 1)).isWrapped) {
|
||||
last++;
|
||||
}
|
||||
return { first, last };
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the tab stops.
|
||||
* @param i The index to start setting up tab stops from.
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { CompositionHelper } from './CompositionHelper';
|
||||
import { ITerminal } from './Types';
|
||||
|
||||
describe('CompositionHelper', () => {
|
||||
let terminal;
|
||||
let compositionHelper;
|
||||
let compositionView;
|
||||
let textarea;
|
||||
let handledText;
|
||||
let terminal: ITerminal;
|
||||
let compositionHelper: CompositionHelper;
|
||||
let compositionView: HTMLElement;
|
||||
let textarea: HTMLTextAreaElement;
|
||||
let handledText: string;
|
||||
|
||||
beforeEach(() => {
|
||||
compositionView = {
|
||||
@@ -27,14 +28,14 @@ describe('CompositionHelper', () => {
|
||||
top: 0
|
||||
},
|
||||
textContent: ''
|
||||
};
|
||||
} as any;
|
||||
textarea = {
|
||||
value: '',
|
||||
style: {
|
||||
left: 0,
|
||||
top: 0
|
||||
}
|
||||
};
|
||||
} as any;
|
||||
terminal = {
|
||||
element: {
|
||||
querySelector: () => {
|
||||
@@ -54,7 +55,7 @@ describe('CompositionHelper', () => {
|
||||
options: {
|
||||
lineHeight: 1
|
||||
}
|
||||
};
|
||||
} as any;
|
||||
handledText = '';
|
||||
compositionHelper = new CompositionHelper(textarea, compositionView, terminal);
|
||||
});
|
||||
@@ -63,7 +64,7 @@ describe('CompositionHelper', () => {
|
||||
it('Should insert simple characters', (done) => {
|
||||
// First character 'ㅇ'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent><CompositionEvent>{ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
@@ -71,7 +72,7 @@ describe('CompositionHelper', () => {
|
||||
assert.equal(handledText, 'ㅇ');
|
||||
// Second character 'ㅇ'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent><CompositionEvent>{ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇㅇ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
@@ -87,13 +88,13 @@ describe('CompositionHelper', () => {
|
||||
it('Should insert complex characters', (done) => {
|
||||
// First character '앙'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '아' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
|
||||
textarea.value = '아';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '앙' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: '앙' });
|
||||
textarea.value = '앙';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
@@ -101,13 +102,13 @@ describe('CompositionHelper', () => {
|
||||
assert.equal(handledText, '앙');
|
||||
// Second character '앙'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
|
||||
textarea.value = '앙ㅇ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '아' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
|
||||
textarea.value = '앙아';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '앙' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: '앙' });
|
||||
textarea.value = '앙앙';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
@@ -127,19 +128,19 @@ describe('CompositionHelper', () => {
|
||||
it('Should insert complex characters that change with following character', (done) => {
|
||||
// First character '아'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '아' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
|
||||
textarea.value = '아';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Start second character '아' in first character
|
||||
compositionHelper.compositionupdate({ data: '앙' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: '앙' });
|
||||
textarea.value = '앙';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: '아' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
|
||||
textarea.value = '아아';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
@@ -156,14 +157,14 @@ describe('CompositionHelper', () => {
|
||||
it('Should insert multi-characters compositions', (done) => {
|
||||
// First character 'だ'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'd' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'd' });
|
||||
textarea.value = 'd';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: 'だ' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だ' });
|
||||
textarea.value = 'だ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Second character 'あ'
|
||||
compositionHelper.compositionupdate({ data: 'だあ' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だあ' });
|
||||
textarea.value = 'だあ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
@@ -179,18 +180,18 @@ describe('CompositionHelper', () => {
|
||||
it('Should insert multi-character compositions that are converted to other characters with the same length', (done) => {
|
||||
// First character 'だ'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'd' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'd' });
|
||||
textarea.value = 'd';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: 'だ' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だ' });
|
||||
textarea.value = 'だ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Second character 'ー'
|
||||
compositionHelper.compositionupdate({ data: 'だー' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だー' });
|
||||
textarea.value = 'だー';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Convert to katakana 'ダー'
|
||||
compositionHelper.compositionupdate({ data: 'ダー' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ダー' });
|
||||
textarea.value = 'ダー';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
@@ -207,18 +208,18 @@ describe('CompositionHelper', () => {
|
||||
it('Should insert multi-character compositions that are converted to other characters with different lengths', (done) => {
|
||||
// First character 'い'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'い' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'い' });
|
||||
textarea.value = 'い';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Second character 'ま'
|
||||
compositionHelper.compositionupdate({ data: 'いm' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'いm' });
|
||||
textarea.value = 'いm';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: 'いま' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'いま' });
|
||||
textarea.value = 'いま';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Convert to kanji '今'
|
||||
compositionHelper.compositionupdate({ data: '今' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: '今' });
|
||||
textarea.value = '今';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
@@ -235,7 +236,7 @@ describe('CompositionHelper', () => {
|
||||
it('Should insert non-composition characters input immediately after composition characters', (done) => {
|
||||
// First character 'ㅇ'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
|
||||
@@ -237,7 +237,7 @@ describe('EscapeSequenceParser', function (): void {
|
||||
'\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f',
|
||||
'\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x99', '\x9a'
|
||||
];
|
||||
const exceptions = {
|
||||
const exceptions: { [key: number]: { [key: string]: any[] } } = {
|
||||
8: { '\x18': [], '\x1a': [] } // simply abort osc state
|
||||
};
|
||||
parser.reset();
|
||||
@@ -247,7 +247,7 @@ describe('EscapeSequenceParser', function (): void {
|
||||
parser.currentState = state;
|
||||
parser.parse(exes[i]);
|
||||
chai.expect(parser.currentState).equal(ParserState.GROUND);
|
||||
testTerminal.compare(((exceptions[state]) ? exceptions[state][exes[i]] : 0) || [['exe', exes[i]]]);
|
||||
testTerminal.compare((state in exceptions ? exceptions[state][exes[i]] : 0) || [['exe', exes[i]]]);
|
||||
parser.reset();
|
||||
testTerminal.clear();
|
||||
}
|
||||
@@ -1099,20 +1099,20 @@ describe('EscapeSequenceParser', function (): void {
|
||||
|
||||
describe('set/clear handler', function (): void {
|
||||
const INPUT = '\x1b[1;31mhello \x1b%Gwor\x1bEld!\x1b[0m\r\n$>\x1b]1;foo=bar\x1b\\';
|
||||
let parser2 = null;
|
||||
let parser2: TestEscapeSequenceParser = null;
|
||||
let print = '';
|
||||
let esc = [];
|
||||
let csi = [];
|
||||
let exe = [];
|
||||
let osc = [];
|
||||
let dcs = [];
|
||||
const esc: string[] = [];
|
||||
const csi: [string, number[], string][] = [];
|
||||
const exe: string[] = [];
|
||||
const osc: [number, string][] = [];
|
||||
const dcs: ([string] | [string, string] | [string, string, number[], number])[] = [];
|
||||
function clearAccu(): void {
|
||||
print = '';
|
||||
esc = [];
|
||||
csi = [];
|
||||
exe = [];
|
||||
osc = [];
|
||||
dcs = [];
|
||||
esc.length = 0;
|
||||
csi.length = 0;
|
||||
exe.length = 0;
|
||||
osc.length = 0;
|
||||
dcs.length = 0;
|
||||
}
|
||||
beforeEach(function (): void {
|
||||
parser2 = new TestEscapeSequenceParser();
|
||||
|
||||
+13
-13
@@ -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';
|
||||
@@ -15,7 +15,7 @@ import { EscapeSequenceParser } from './EscapeSequenceParser';
|
||||
/**
|
||||
* Map collect to glevel. Used in `selectCharset`.
|
||||
*/
|
||||
const GLEVEL = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2};
|
||||
const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2};
|
||||
|
||||
|
||||
/**
|
||||
@@ -77,7 +77,7 @@ class DECRQSS implements IDcsHandler {
|
||||
// TODO: report real settings instead of 0m
|
||||
return this._terminal.send(`${C0.ESC}P1$r0m${C0.ESC}\\`);
|
||||
case ' q': // DECSCUSR
|
||||
const STYLES = {'block': 2, 'underline': 4, 'bar': 6};
|
||||
const STYLES: {[key: string]: number} = {'block': 2, 'underline': 4, 'bar': 6};
|
||||
let style = STYLES[this._terminal.getOption('cursorStyle')];
|
||||
style -= this._terminal.getOption('cursorBlink');
|
||||
return this._terminal.send(`${C0.ESC}P1$r${style} q${C0.ESC}\\`);
|
||||
@@ -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; // ??
|
||||
|
||||
@@ -298,6 +298,16 @@ describe('SelectionManager', () => {
|
||||
assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]);
|
||||
assert.deepEqual(selectionManager.model.finalSelectionEnd, [terminal.cols, 0], 'The actual selection spans the entire column');
|
||||
});
|
||||
it('should select the entire wrapped line', () => {
|
||||
buffer.lines.set(0, stringToRow('foo'));
|
||||
const line2 = stringToRow('bar');
|
||||
(<any>line2).isWrapped = true;
|
||||
buffer.lines.set(1, line2);
|
||||
selectionManager.selectLineAt(0);
|
||||
assert.equal(selectionManager.selectionText, 'foobar', 'The selected text is correct');
|
||||
assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]);
|
||||
assert.deepEqual(selectionManager.model.finalSelectionEnd, [terminal.cols, 1], 'The actual selection spans the entire column');
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectAll', () => {
|
||||
|
||||
@@ -803,7 +803,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
* @param line The line index.
|
||||
*/
|
||||
protected _selectLineAt(line: number): void {
|
||||
this._model.selectionStart = [0, line];
|
||||
this._model.selectionStartLength = this._terminal.cols;
|
||||
const wrappedRange = this._buffer.getWrappedRangeForLine(line);
|
||||
this._model.selectionStart = [0, wrappedRange.first];
|
||||
this._model.selectionEnd = [this._terminal.cols, wrappedRange.last];
|
||||
this._model.selectionStartLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@ import * as pty from 'node-pty';
|
||||
import { assert } from 'chai';
|
||||
import { Terminal } from './Terminal';
|
||||
import { CHAR_DATA_CHAR_INDEX } from './Buffer';
|
||||
import { IViewport } from './Types';
|
||||
|
||||
class TestTerminal extends Terminal {
|
||||
innerWrite(): void { this._innerWrite(); }
|
||||
}
|
||||
|
||||
let primitivePty: any;
|
||||
|
||||
@@ -87,12 +92,12 @@ if (os.platform() !== 'win32') {
|
||||
|
||||
/** tests */
|
||||
describe('xterm output comparison', () => {
|
||||
let xterm;
|
||||
let xterm: TestTerminal;
|
||||
|
||||
beforeEach(() => {
|
||||
xterm = new Terminal({ cols: COLS, rows: ROWS });
|
||||
xterm = new TestTerminal({ cols: COLS, rows: ROWS });
|
||||
xterm.refresh = () => {};
|
||||
xterm.viewport = {
|
||||
xterm.viewport = <IViewport>{
|
||||
syncScrollArea: () => {}
|
||||
};
|
||||
});
|
||||
@@ -125,7 +130,7 @@ if (os.platform() !== 'win32') {
|
||||
|
||||
// Perform a synchronous .write(data)
|
||||
xterm.writeBuffer.push(fromPty);
|
||||
xterm._innerWrite();
|
||||
xterm.innerWrite();
|
||||
|
||||
const fromEmulator = terminalToString(xterm);
|
||||
console.log = CONSOLE_LOG;
|
||||
|
||||
@@ -176,7 +176,7 @@ describe('term.js addons', () => {
|
||||
|
||||
describe('scroll', () => {
|
||||
describe('scrollLines', () => {
|
||||
let startYDisp;
|
||||
let startYDisp: number;
|
||||
beforeEach(() => {
|
||||
for (let i = 0; i < term.rows * 2; i++) {
|
||||
term.writeln('test');
|
||||
@@ -211,7 +211,7 @@ describe('term.js addons', () => {
|
||||
});
|
||||
|
||||
describe('scrollPages', () => {
|
||||
let startYDisp;
|
||||
let startYDisp: number;
|
||||
beforeEach(() => {
|
||||
for (let i = 0; i < term.rows * 3; i++) {
|
||||
term.writeln('test');
|
||||
@@ -248,7 +248,7 @@ describe('term.js addons', () => {
|
||||
});
|
||||
|
||||
describe('scrollToBottom', () => {
|
||||
let startYDisp;
|
||||
let startYDisp: number;
|
||||
beforeEach(() => {
|
||||
for (let i = 0; i < term.rows * 3; i++) {
|
||||
term.writeln('test');
|
||||
@@ -269,7 +269,7 @@ describe('term.js addons', () => {
|
||||
});
|
||||
|
||||
describe('scrollToLine', () => {
|
||||
let startYDisp;
|
||||
let startYDisp: number;
|
||||
beforeEach(() => {
|
||||
for (let i = 0; i < term.rows * 3; i++) {
|
||||
term.writeln('test');
|
||||
@@ -712,8 +712,8 @@ describe('term.js addons', () => {
|
||||
});
|
||||
|
||||
describe('Third level shift', () => {
|
||||
let evKeyDown;
|
||||
let evKeyPress;
|
||||
let evKeyDown: any;
|
||||
let evKeyPress: any;
|
||||
|
||||
beforeEach(() => {
|
||||
term.handler = () => {};
|
||||
|
||||
+20
-23
@@ -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,9 +49,10 @@ 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 = {
|
||||
const KEYCODE_KEY_MAPPINGS: { [key: number]: [string, string]} = {
|
||||
// digits 0-9
|
||||
48: ['0', ')'],
|
||||
49: ['1', '!'],
|
||||
@@ -128,7 +129,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 {
|
||||
@@ -195,7 +197,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)[];
|
||||
@@ -282,8 +283,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
if (this.options[key] == null) {
|
||||
this.options[key] = DEFAULT_OPTIONS[key];
|
||||
}
|
||||
// TODO: We should move away from duplicate options on the Terminal object
|
||||
this[key] = this.options[key];
|
||||
});
|
||||
|
||||
// this.context = options.context || window;
|
||||
@@ -318,8 +317,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;
|
||||
@@ -363,8 +361,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,11 +387,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
throw new Error('No option with key "' + key + '"');
|
||||
}
|
||||
|
||||
if (typeof this.options[key] !== 'undefined') {
|
||||
return this.options[key];
|
||||
}
|
||||
|
||||
return this[key];
|
||||
return this.options[key];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -470,7 +464,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
}
|
||||
break;
|
||||
}
|
||||
this[key] = value;
|
||||
this.options[key] = value;
|
||||
switch (key) {
|
||||
case 'fontFamily':
|
||||
@@ -569,7 +562,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
}
|
||||
copyHandler(event, this, this.selectionManager);
|
||||
});
|
||||
const pasteHandlerWrapper = event => pasteHandler(event, this);
|
||||
const pasteHandlerWrapper = (event: ClipboardEvent) => pasteHandler(event, this);
|
||||
on(this.textarea, 'paste', pasteHandlerWrapper);
|
||||
on(this.element, 'paste', pasteHandlerWrapper);
|
||||
|
||||
@@ -708,7 +701,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);
|
||||
@@ -721,7 +718,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);
|
||||
@@ -1300,7 +1297,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
}
|
||||
}
|
||||
|
||||
private _innerWrite(): void {
|
||||
protected _innerWrite(): void {
|
||||
const writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);
|
||||
while (writeBatch.length > 0) {
|
||||
const data = writeBatch.shift();
|
||||
@@ -2065,7 +2062,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 = [];
|
||||
@@ -2085,14 +2082,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) */];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -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;
|
||||
@@ -261,6 +259,7 @@ export interface ICharMeasure {
|
||||
|
||||
// TODO: The options that are not in the public API should be reviewed
|
||||
export interface ITerminalOptions extends IPublicTerminalOptions {
|
||||
[key: string]: any;
|
||||
cancelEvents?: boolean;
|
||||
convertEol?: boolean;
|
||||
debug?: boolean;
|
||||
@@ -284,6 +283,7 @@ export interface IBuffer {
|
||||
savedX: number;
|
||||
isCursorInViewport: boolean;
|
||||
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string;
|
||||
getWrappedRangeForLine(y: number): { first: number, last: number };
|
||||
nextStop(x?: number): number;
|
||||
prevStop(x?: number): number;
|
||||
}
|
||||
|
||||
@@ -36,10 +36,11 @@ export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean
|
||||
}
|
||||
};
|
||||
|
||||
let myTextDecoder;
|
||||
// TODO: This should be typed but there seem to be issues importing the type
|
||||
let myTextDecoder: any;
|
||||
|
||||
addonTerminal.__getMessage = function(ev: MessageEvent): void {
|
||||
let str;
|
||||
let str: string;
|
||||
|
||||
if (typeof ev.data === 'object') {
|
||||
if (!myTextDecoder) {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
## fit addon
|
||||
|
||||
The fit addon adjusts the dimensions of the terminal to match best fit its parent element container. `fit` will only work when the element is visible.
|
||||
@@ -36,7 +36,7 @@ describe('ColorManager', () => {
|
||||
for (const key of Object.keys(cm.colors)) {
|
||||
if (key !== 'ansi') {
|
||||
// A #rrggbb or rgba(...)
|
||||
assert.ok(cm.colors[key].css.length >= 7);
|
||||
assert.ok((<any>cm.colors)[key].css.length >= 7);
|
||||
}
|
||||
}
|
||||
assert.equal(cm.colors.ansi.length, 256);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ICharAtlasConfig } from '../../shared/atlas/Types';
|
||||
|
||||
export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig {
|
||||
// null out some fields that don't matter
|
||||
const clonedColors = {
|
||||
const clonedColors = <IColorSet>{
|
||||
foreground: colors.foreground,
|
||||
background: colors.background,
|
||||
cursor: null,
|
||||
|
||||
@@ -11,7 +11,7 @@ interface ILinkedListNode<T> {
|
||||
}
|
||||
|
||||
export default class LRUMap<T> {
|
||||
private _map = {};
|
||||
private _map: { [key: string]: ILinkedListNode<T> } = {};
|
||||
private _head: ILinkedListNode<T> = null;
|
||||
private _tail: ILinkedListNode<T> = null;
|
||||
private _nodePool: ILinkedListNode<T>[] = [];
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user