Merge pull request #839 from Tyriar/335_xterm_to_ts

Convert xterm.js to TypeScript
This commit is contained in:
Daniel Imms
2017-08-06 10:49:27 -07:00
committed by GitHub
36 changed files with 3220 additions and 2721 deletions
+123 -6
View File
@@ -1,31 +1,148 @@
/**
* @license MIT
*/
import { assert } from 'chai';
import { ITerminal } from './Interfaces';
import { Buffer } from './Buffer';
import { CircularList } from './utils/CircularList';
import { MockTerminal } from './utils/TestUtils';
const INIT_COLS = 80;
const INIT_ROWS = 24;
describe('Buffer', () => {
let terminal: ITerminal;
let buffer: Buffer;
beforeEach(() => {
terminal = <any>{
cols: 80,
rows: 24,
scrollback: 1000
};
terminal = new MockTerminal();
terminal.cols = INIT_COLS;
terminal.rows = INIT_ROWS;
terminal.options.scrollback = 1000;
buffer = new Buffer(terminal);
});
describe('constructor', () => {
it('should create a CircularList with max length equal to scrollback, for its lines', () => {
assert.instanceOf(buffer.lines, CircularList);
assert.equal(buffer.lines.maxLength, terminal.scrollback);
assert.equal(buffer.lines.maxLength, terminal.options.scrollback);
});
it('should set the Buffer\'s scrollBottom value equal to the terminal\'s rows -1', () => {
assert.equal(buffer.scrollBottom, terminal.rows - 1);
});
});
describe('fillViewportRows', () => {
it('should fill the buffer with blank lines based on the size of the viewport', () => {
const blankLineChar = terminal.blankLine()[0];
buffer.fillViewportRows();
assert.equal(buffer.lines.length, INIT_ROWS);
for (let y = 0; y < INIT_ROWS; y++) {
assert.equal(buffer.lines.get(y).length, INIT_COLS);
for (let x = 0; x < INIT_COLS; x++) {
assert.deepEqual(buffer.lines.get(y)[x], blankLineChar);
}
}
});
});
describe('resize', () => {
describe('column size is reduced', () => {
it('should not trim the data in the buffer', () => {
buffer.fillViewportRows();
buffer.resize(INIT_COLS / 2, INIT_ROWS);
assert.equal(buffer.lines.length, INIT_ROWS);
for (let i = 0; i < INIT_ROWS; i++) {
assert.equal(buffer.lines.get(i).length, INIT_COLS);
}
});
});
describe('column size is increased', () => {
it('should add pad columns', () => {
buffer.fillViewportRows();
buffer.resize(INIT_COLS + 10, INIT_ROWS);
assert.equal(buffer.lines.length, INIT_ROWS);
for (let i = 0; i < INIT_ROWS; i++) {
assert.equal(buffer.lines.get(i).length, INIT_COLS + 10);
}
});
});
describe('row size reduced', () => {
it('should trim blank lines from the end', () => {
buffer.fillViewportRows();
buffer.resize(INIT_COLS, INIT_ROWS - 10);
assert.equal(buffer.lines.length, INIT_ROWS - 10);
});
it('should move the viewport down when it\'s at the end', () => {
buffer.fillViewportRows();
// Set cursor y to have 5 blank lines below it
buffer.y = INIT_ROWS - 5 - 1;
buffer.resize(INIT_COLS, INIT_ROWS - 10);
// Trim 5 rows
assert.equal(buffer.lines.length, INIT_ROWS - 5);
// Shift the viewport down 5 rows
assert.equal(buffer.ydisp, 5);
assert.equal(buffer.ybase, 5);
});
});
describe('row size increased', () => {
describe('empty buffer', () => {
it('should add blank lines to end', () => {
buffer.fillViewportRows();
assert.equal(buffer.ydisp, 0);
buffer.resize(INIT_COLS, INIT_ROWS + 10);
assert.equal(buffer.ydisp, 0);
assert.equal(buffer.lines.length, INIT_ROWS + 10);
});
});
describe('filled buffer', () => {
it('should show more of the buffer above', () => {
buffer.fillViewportRows();
// Create 10 extra blank lines
for (let i = 0; i < 10; i++) {
buffer.lines.push(terminal.blankLine());
}
// Set cursor to the bottom of the buffer
buffer.y = INIT_ROWS - 1;
// Scroll down 10 lines
buffer.ybase = 10;
buffer.ydisp = 10;
assert.equal(buffer.lines.length, INIT_ROWS + 10);
buffer.resize(INIT_COLS, INIT_ROWS + 5);
// Should be should 5 more lines
assert.equal(buffer.ydisp, 5);
assert.equal(buffer.ybase, 5);
// Should not trim the buffer
assert.equal(buffer.lines.length, INIT_ROWS + 10);
});
it('should show more of the buffer below when the viewport is at the top of the buffer', () => {
buffer.fillViewportRows();
// Create 10 extra blank lines
for (let i = 0; i < 10; i++) {
buffer.lines.push(terminal.blankLine());
}
// Set cursor to the bottom of the buffer
buffer.y = INIT_ROWS - 1;
// Scroll down 10 lines
buffer.ybase = 10;
buffer.ydisp = 0;
assert.equal(buffer.lines.length, INIT_ROWS + 10);
buffer.resize(INIT_COLS, INIT_ROWS + 5);
// The viewport should remain at the top
assert.equal(buffer.ydisp, 0);
// The buffer ybase should move up 5 lines
assert.equal(buffer.ybase, 5);
// Should not trim the buffer
assert.equal(buffer.lines.length, INIT_ROWS + 10);
});
});
});
});
});
+110 -14
View File
@@ -2,8 +2,9 @@
* @license MIT
*/
import { ITerminal } from './Interfaces';
import { ITerminal, IBuffer } from './Interfaces';
import { CircularList } from './utils/CircularList';
import { LineData, CharData } from './Types';
/**
* This class represents a terminal buffer (an internal state of the terminal), where the
@@ -12,31 +13,126 @@ import { CircularList } from './utils/CircularList';
* - cursor position
* - scroll position
*/
export class Buffer {
public lines: CircularList<[number, string, number][]>;
export class Buffer implements IBuffer {
private _lines: CircularList<LineData>;
public ydisp: number;
public ybase: number;
public y: number;
public x: number;
public scrollBottom: number;
public scrollTop: number;
public tabs: any;
public savedY: number;
public savedX: number;
/**
* Create a new Buffer.
* @param {Terminal} terminal - The terminal the Buffer will belong to
* @param {Terminal} _terminal - The terminal the Buffer will belong to
* @param {number} ydisp - The scroll position of the Buffer in the viewport
* @param {number} ybase - The scroll position of the y cursor (ybase + y = the y position within the Buffer)
* @param {number} y - The cursor's y position after ybase
* @param {number} x - The cursor's x position after ybase
*/
constructor(
private terminal: ITerminal,
public ydisp: number = 0,
public ybase: number = 0,
public y: number = 0,
public x: number = 0,
public scrollBottom: number = 0,
public scrollTop: number = 0,
public tabs: any = {},
private _terminal: ITerminal
) {
this.lines = new CircularList<[number, string, number][]>(this.terminal.scrollback);
this.scrollBottom = this.terminal.rows - 1;
this.clear();
}
public get lines(): CircularList<LineData> {
return this._lines;
}
public fillViewportRows(): void {
if (this._lines.length === 0) {
let i = this._terminal.rows;
while (i--) {
this.lines.push(this._terminal.blankLine());
}
}
}
public clear(): void {
this.ydisp = 0;
this.ybase = 0;
this.y = 0;
this.x = 0;
this.scrollBottom = 0;
this.scrollTop = 0;
this.tabs = {};
this._lines = new CircularList<LineData>(this._terminal.options.scrollback);
this.scrollBottom = this._terminal.rows - 1;
}
public resize(newCols: number, newRows: number): void {
// Don't resize the buffer if it's empty and hasn't been used yet.
if (this._lines.length === 0) {
return;
}
// Deal with columns increasing (we don't do anything when columns reduce)
if (this._terminal.cols < newCols) {
const ch: CharData = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr?
for (let i = 0; i < this._lines.length; i++) {
if (this._lines.get(i) === undefined) {
this._lines.set(i, this._terminal.blankLine());
}
while (this._lines.get(i).length < newCols) {
this._lines.get(i).push(ch);
}
}
}
// Resize rows in both directions as needed
let addToY = 0;
if (this._terminal.rows < newRows) {
for (let y = this._terminal.rows; y < newRows; y++) {
if (this._lines.length < newRows + this.ybase) {
if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) {
// There is room above the buffer and there are no empty elements below the line,
// scroll up
this.ybase--;
addToY++;
if (this.ydisp > 0) {
// Viewport is at the top of the buffer, must increase downwards
this.ydisp--;
}
} else {
// Add a blank line if there is no buffer left at the top to scroll to, or if there
// are blank lines after the cursor
this._lines.push(this._terminal.blankLine());
}
}
}
} else { // (this._terminal.rows >= newRows)
for (let y = this._terminal.rows; y > newRows; y--) {
if (this._lines.length > newRows + this.ybase) {
if (this._lines.length > this.ybase + this.y + 1) {
// The line is a blank line below the cursor, remove it
this._lines.pop();
} else {
// The line is the cursor, scroll down
this.ybase++;
this.ydisp++;
}
}
}
}
// Make sure that the cursor stays on screen
if (this.y >= newRows) {
this.y = newRows - 1;
}
if (addToY) {
this.y += addToY;
}
if (this.x >= newCols) {
this.x = newCols - 1;
}
this.scrollTop = 0;
this.scrollBottom = newRows - 1;
}
}
+6 -5
View File
@@ -1,21 +1,22 @@
/**
* @license MIT
*/
import { assert } from 'chai';
import { ITerminal } from './Interfaces';
import { BufferSet } from './BufferSet';
import { Buffer } from './Buffer';
import { MockTerminal } from './utils/TestUtils';
describe('BufferSet', () => {
let terminal: ITerminal;
let bufferSet: BufferSet;
beforeEach(() => {
terminal = <any>{
cols: 80,
rows: 24,
scrollback: 1000
};
terminal = new MockTerminal();
terminal.cols = 80;
terminal.rows = 24;
terminal.options.scrollback = 1000;
bufferSet = new BufferSet(terminal);
});
+15
View File
@@ -22,6 +22,7 @@ export class BufferSet extends EventEmitter implements IBufferSet {
constructor(private _terminal: ITerminal) {
super();
this._normal = new Buffer(this._terminal);
this._normal.fillViewportRows();
this._alt = new Buffer(this._terminal);
this._activeBuffer = this._normal;
}
@@ -54,6 +55,11 @@ export class BufferSet extends EventEmitter implements IBufferSet {
* Sets the normal Buffer of the BufferSet as its currently active Buffer
*/
public activateNormalBuffer(): void {
// The alt buffer should always be cleared when we switch to the normal
// buffer. This frees up memory since the alt buffer should always be new
// when activated.
this._alt.clear();
this._activeBuffer = this._normal;
this.emit('activate', this._normal);
}
@@ -62,7 +68,16 @@ export class BufferSet extends EventEmitter implements IBufferSet {
* Sets the alt Buffer of the BufferSet as its currently active Buffer
*/
public activateAltBuffer(): void {
// Since the alt buffer is always cleared when the normal buffer is
// activated, we want to fill it when switching to it.
this._alt.fillViewportRows();
this._activeBuffer = this._alt;
this.emit('activate', this._alt);
}
public resize(newCols: number, newRows: number): void {
this._normal.resize(newCols, newRows);
this._alt.resize(newCols, newRows);
}
}
+4 -2
View File
@@ -2,17 +2,19 @@
* @license MIT
*/
import { Charset } from './Types';
/**
* The character sets supported by the terminal. These enable several languages
* to be represented within the terminal with only 8-bit encoding. See ISO 2022
* for a discussion on character sets. Only VT100 character sets are supported.
*/
export const CHARSETS: {[key: string]: {[key: string]: string}} = {};
export const CHARSETS: { [key: string]: Charset } = {};
/**
* The default character set, US.
*/
export const DEFAULT_CHARSET = CHARSETS['B'];
export const DEFAULT_CHARSET: Charset = CHARSETS['B'];
/**
* DEC Special Character and Line Drawing Set.
+12 -8
View File
@@ -1,3 +1,7 @@
/**
* @license MIT
*/
import { assert } from 'chai';
import { CompositionHelper } from './CompositionHelper';
@@ -36,7 +40,7 @@ describe('CompositionHelper', () => {
return { offsetLeft: 0, offsetTop: 0 };
}
},
handler: function (text) {
handler: (text: string) => {
handledText += text;
}
};
@@ -45,7 +49,7 @@ describe('CompositionHelper', () => {
});
describe('Input', () => {
it('Should insert simple characters', function (done) {
it('Should insert simple characters', (done) => {
// First character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
@@ -69,7 +73,7 @@ describe('CompositionHelper', () => {
}, 0);
});
it('Should insert complex characters', function (done) {
it('Should insert complex characters', (done) => {
// First character '앙'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
@@ -109,7 +113,7 @@ describe('CompositionHelper', () => {
}, 0);
});
it('Should insert complex characters that change with following character', function (done) {
it('Should insert complex characters that change with following character', (done) => {
// First character '아'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
@@ -138,7 +142,7 @@ describe('CompositionHelper', () => {
}, 0);
});
it('Should insert multi-characters compositions', function (done) {
it('Should insert multi-characters compositions', (done) => {
// First character 'だ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'd' });
@@ -161,7 +165,7 @@ describe('CompositionHelper', () => {
}, 0);
});
it('Should insert multi-character compositions that are converted to other characters with the same length', function (done) {
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' });
@@ -189,7 +193,7 @@ describe('CompositionHelper', () => {
}, 0);
});
it('Should insert multi-character compositions that are converted to other characters with different lengths', function (done) {
it('Should insert multi-character compositions that are converted to other characters with different lengths', (done) => {
// First character 'い'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'い' });
@@ -217,7 +221,7 @@ describe('CompositionHelper', () => {
}, 0);
});
it('Should insert non-composition characters input immediately after composition characters', function (done) {
it('Should insert non-composition characters input immediately after composition characters', (done) => {
// First character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate({ data: 'ㅇ' });
+8 -8
View File
@@ -51,7 +51,7 @@ export class CompositionHelper {
/**
* Handles the compositionstart event, activating the composition view.
*/
public compositionstart() {
public compositionstart(): void {
this.isComposing = true;
this.compositionPosition.start = this.textarea.value.length;
this.compositionView.textContent = '';
@@ -62,7 +62,7 @@ export class CompositionHelper {
* Handles the compositionupdate event, updating the composition view.
* @param {CompositionEvent} ev The event.
*/
public compositionupdate(ev: CompositionEvent) {
public compositionupdate(ev: CompositionEvent): void {
this.compositionView.textContent = ev.data;
this.updateCompositionElements();
setTimeout(() => {
@@ -74,7 +74,7 @@ export class CompositionHelper {
* Handles the compositionend event, hiding the composition view and sending the composition to
* the handler.
*/
public compositionend() {
public compositionend(): void {
this.finalizeComposition(true);
}
@@ -83,7 +83,7 @@ export class CompositionHelper {
* @param ev The keydown event.
* @return Whether the Terminal should continue processing the keydown event.
*/
public keydown(ev: KeyboardEvent) {
public keydown(ev: KeyboardEvent): boolean {
if (this.isComposing || this.isSendingComposition) {
if (ev.keyCode === 229) {
// Continue composing if the keyCode is the "composition character"
@@ -116,7 +116,7 @@ export class CompositionHelper {
* compositionend event is triggered, such as enter, so that the composition is send before
* the command is executed.
*/
private finalizeComposition(waitForPropogation: boolean) {
private finalizeComposition(waitForPropogation: boolean): void {
this.compositionView.classList.remove('active');
this.isComposing = false;
this.clearTextareaPosition();
@@ -169,7 +169,7 @@ export class CompositionHelper {
* character" (229) is triggered, in order to allow non-composition text to be entered when an
* IME is active.
*/
private handleAnyTextareaChanges() {
private handleAnyTextareaChanges(): void {
const oldValue = this.textarea.value;
setTimeout(() => {
// Ignore if a composition has started since the timeout
@@ -189,7 +189,7 @@ export class CompositionHelper {
* @param dontRecurse Whether to use setTimeout to recursively trigger another update, this is
* necessary as the IME events across browsers are not consistently triggered.
*/
public updateCompositionElements(dontRecurse?: boolean) {
public updateCompositionElements(dontRecurse?: boolean): void {
if (!this.isComposing) {
return;
}
@@ -222,7 +222,7 @@ export class CompositionHelper {
* Clears the textarea's position so that the cursor does not blink on IE.
* @private
*/
private clearTextareaPosition() {
private clearTextareaPosition(): void {
this.textarea.style.left = '';
this.textarea.style.top = '';
};
+4
View File
@@ -1,3 +1,7 @@
/**
* @license MIT
*/
import { assert } from 'chai';
import { EventEmitter } from './EventEmitter';
+14 -15
View File
@@ -2,15 +2,10 @@
* @license MIT
*/
import { IEventEmitter } from './Interfaces';
interface ListenerType {
(): void;
listener?: () => void;
};
import { IEventEmitter, IListenerType } from './Interfaces';
export class EventEmitter implements IEventEmitter {
private _events: {[type: string]: ListenerType[]};
private _events: {[type: string]: IListenerType[]};
constructor() {
// Restore the previous events if available, this will happen if the
@@ -18,12 +13,12 @@ export class EventEmitter implements IEventEmitter {
this._events = this._events || {};
}
public on(type, listener): void {
public on(type: string, listener: IListenerType): void {
this._events[type] = this._events[type] || [];
this._events[type].push(listener);
}
public off(type, listener): void {
public off(type: string, listener: IListenerType): void {
if (!this._events[type]) {
return;
}
@@ -39,20 +34,20 @@ export class EventEmitter implements IEventEmitter {
}
}
public removeAllListeners(type): void {
public removeAllListeners(type: string): void {
if (this._events[type]) {
delete this._events[type];
}
}
public once(type, listener): any {
function on() {
public once(type: string, listener: IListenerType): void {
function on(): void {
let args = Array.prototype.slice.call(arguments);
this.off(type, on);
return listener.apply(this, args);
listener.apply(this, args);
}
(<any>on).listener = listener;
return this.on(type, on);
this.on(type, on);
}
public emit(type: string, ...args: any[]): void {
@@ -65,7 +60,11 @@ export class EventEmitter implements IEventEmitter {
}
}
public listeners(type): ListenerType[] {
public listeners(type: string): IListenerType[] {
return this._events[type] || [];
}
protected destroy(): void {
this._events = {};
}
}
+50 -44
View File
@@ -1,10 +1,17 @@
/**
* @license MIT
*/
import { assert } from 'chai';
import { InputHandler } from './InputHandler';
import { wcwidth } from './InputHandler';
import { MockInputHandlingTerminal } from './utils/TestUtils';
describe('InputHandler', () => {
describe('save and restore cursor', () => {
let terminal = { buffer: { x: 1, y: 2 } };
let terminal = new MockInputHandlingTerminal();
terminal.buffer.x = 1;
terminal.buffer.y = 2;
let inputHandler = new InputHandler(terminal);
// Save cursor position
inputHandler.saveCursor([]);
@@ -20,51 +27,47 @@ describe('InputHandler', () => {
});
describe('setCursorStyle', () => {
it('should call Terminal.setOption with correct params', () => {
let options = {};
let terminal = {
setOption: (option, value) => options[option] = value
};
let terminal = new MockInputHandlingTerminal();
let inputHandler = new InputHandler(terminal);
inputHandler.setCursorStyle([0]);
assert.equal(options['cursorStyle'], 'block');
assert.equal(options['cursorBlink'], true);
assert.equal(terminal.options['cursorStyle'], 'block');
assert.equal(terminal.options['cursorBlink'], true);
options = {};
terminal.options = {};
inputHandler.setCursorStyle([1]);
assert.equal(options['cursorStyle'], 'block');
assert.equal(options['cursorBlink'], true);
assert.equal(terminal.options['cursorStyle'], 'block');
assert.equal(terminal.options['cursorBlink'], true);
options = {};
terminal.options = {};
inputHandler.setCursorStyle([2]);
assert.equal(options['cursorStyle'], 'block');
assert.equal(options['cursorBlink'], false);
assert.equal(terminal.options['cursorStyle'], 'block');
assert.equal(terminal.options['cursorBlink'], false);
options = {};
terminal.options = {};
inputHandler.setCursorStyle([3]);
assert.equal(options['cursorStyle'], 'underline');
assert.equal(options['cursorBlink'], true);
assert.equal(terminal.options['cursorStyle'], 'underline');
assert.equal(terminal.options['cursorBlink'], true);
options = {};
terminal.options = {};
inputHandler.setCursorStyle([4]);
assert.equal(options['cursorStyle'], 'underline');
assert.equal(options['cursorBlink'], false);
assert.equal(terminal.options['cursorStyle'], 'underline');
assert.equal(terminal.options['cursorBlink'], false);
options = {};
terminal.options = {};
inputHandler.setCursorStyle([5]);
assert.equal(options['cursorStyle'], 'bar');
assert.equal(options['cursorBlink'], true);
assert.equal(terminal.options['cursorStyle'], 'bar');
assert.equal(terminal.options['cursorBlink'], true);
options = {};
terminal.options = {};
inputHandler.setCursorStyle([6]);
assert.equal(options['cursorStyle'], 'bar');
assert.equal(options['cursorBlink'], false);
assert.equal(terminal.options['cursorStyle'], 'bar');
assert.equal(terminal.options['cursorBlink'], false);
});
});
});
const old_wcwidth = (function(opts) {
const old_wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number {
// extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c
// combining characters
const COMBINING = [
@@ -118,7 +121,7 @@ const old_wcwidth = (function(opts) {
[0xE0100, 0xE01EF]
];
// binary search
function bisearch(ucs) {
function bisearch(ucs: number): boolean {
let min = 0;
let max = COMBINING.length - 1;
let mid;
@@ -135,23 +138,26 @@ const old_wcwidth = (function(opts) {
}
return false;
}
function wcwidth(ucs) {
// test for 8-bit control characters
if (ucs === 0)
return opts.nul;
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
return opts.control;
// binary search in table of non-spacing characters
if (bisearch(ucs))
return 0;
// if we arrive here, ucs is not a combining or C0/C1 control character
if (isWide(ucs)) {
return 2;
}
return 1;
function wcwidth(ucs: number): number {
// test for 8-bit control characters
if (ucs === 0) {
return opts.nul;
}
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) {
return opts.control;
}
// binary search in table of non-spacing characters
if (bisearch(ucs)) {
return 0;
}
// if we arrive here, ucs is not a combining or C0/C1 control character
if (isWide(ucs)) {
return 2;
}
return 1;
}
function isWide(ucs) {
return (
function isWide(ucs: number): boolean {
return (
ucs >= 0x1100 && (
ucs <= 0x115f || // Hangul Jamo init. consonants
ucs === 0x2329 ||
+48 -59
View File
@@ -2,9 +2,10 @@
* @license MIT
*/
import { IInputHandler, ITerminal } from './Interfaces';
import { IInputHandler, ITerminal, IInputHandlingTerminal } from './Interfaces';
import { C0 } from './EscapeSequences';
import { DEFAULT_CHARSET } from './Charsets';
import { CharData } from './Types';
/**
* The terminal's standard implementation of IInputHandler, this handles all
@@ -14,8 +15,7 @@ import { DEFAULT_CHARSET } from './Charsets';
* each function's header comment.
*/
export class InputHandler implements IInputHandler {
// TODO: We want to type _terminal when it's pulled into TS
constructor(private _terminal: any) { }
constructor(private _terminal: IInputHandlingTerminal) { }
public addChar(char: string, code: number): void {
if (char >= ' ') {
@@ -61,7 +61,7 @@ export class InputHandler implements IInputHandler {
} else {
// The line already exists (eg. the initial viewport), mark it as a
// wrapped line
this._terminal.buffer.lines.get(this._terminal.buffer.y).isWrapped = true;
(<any>this._terminal.buffer.lines.get(this._terminal.buffer.y)).isWrapped = true;
}
} else {
if (ch_width === 2) // FIXME: check for xterm behavior
@@ -105,12 +105,12 @@ export class InputHandler implements IInputHandler {
* Bell (Ctrl-G).
*/
public bell(): void {
if (!this._terminal.visualBell) {
if (!this._terminal.options.visualBell) {
return;
}
this._terminal.element.style.borderColor = 'white';
setTimeout(() => this._terminal.element.style.borderColor = '', 10);
if (this._terminal.popOnBell) {
if (this._terminal.options.popOnBell) {
this._terminal.focus();
}
}
@@ -189,14 +189,12 @@ export class InputHandler implements IInputHandler {
* Insert Ps (Blank) Character(s) (default = 1) (ICH).
*/
public insertChars(params: number[]): void {
let param, row, j, ch;
param = params[0];
let param = params[0];
if (param < 1) param = 1;
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
j = this._terminal.buffer.x;
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
const row = this._terminal.buffer.y + this._terminal.buffer.ybase;
let j = this._terminal.buffer.x;
const ch: CharData = [this._terminal.eraseAttr(), ' ', 1]; // xterm
while (param-- && j < this._terminal.cols) {
this._terminal.buffer.lines.get(row).splice(j++, 0, ch);
@@ -223,7 +221,7 @@ export class InputHandler implements IInputHandler {
* CSI Ps B
* Cursor Down Ps Times (default = 1) (CUD).
*/
public cursorDown(params: number[]) {
public cursorDown(params: number[]): void {
let param = params[0];
if (param < 1) {
param = 1;
@@ -242,7 +240,7 @@ export class InputHandler implements IInputHandler {
* CSI Ps C
* Cursor Forward Ps Times (default = 1) (CUF).
*/
public cursorForward(params: number[]) {
public cursorForward(params: number[]): void {
let param = params[0];
if (param < 1) {
param = 1;
@@ -257,7 +255,7 @@ export class InputHandler implements IInputHandler {
* CSI Ps D
* Cursor Backward Ps Times (default = 1) (CUB).
*/
public cursorBackward(params: number[]) {
public cursorBackward(params: number[]): void {
let param = params[0];
if (param < 1) {
param = 1;
@@ -325,9 +323,8 @@ export class InputHandler implements IInputHandler {
* Cursor Position [row;column] (default = [1,1]) (CUP).
*/
public cursorPosition(params: number[]): void {
let row, col;
row = params[0] - 1;
let col: number;
let row: number = params[0] - 1;
if (params.length >= 2) {
col = params[1] - 1;
@@ -439,14 +436,13 @@ export class InputHandler implements IInputHandler {
* Insert Ps Line(s) (default = 1) (IL).
*/
public insertLines(params: number[]): void {
let param, row, j;
param = params[0];
let param: number = params[0];
if (param < 1) {
param = 1;
}
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
let row: number = this._terminal.buffer.y + this._terminal.buffer.ybase;
let j: number;
j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;
j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j + 1;
@@ -475,14 +471,13 @@ export class InputHandler implements IInputHandler {
* Delete Ps Line(s) (default = 1) (DL).
*/
public deleteLines(params: number[]): void {
let param, row, j;
param = params[0];
let param = params[0];
if (param < 1) {
param = 1;
}
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
const row: number = this._terminal.buffer.y + this._terminal.buffer.ybase;
let j: number;
j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;
j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j;
@@ -509,15 +504,13 @@ export class InputHandler implements IInputHandler {
* Delete Ps Character(s) (default = 1) (DCH).
*/
public deleteChars(params: number[]): void {
let param, row, ch;
param = params[0];
let param: number = params[0];
if (param < 1) {
param = 1;
}
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
const row = this._terminal.buffer.y + this._terminal.buffer.ybase;
const ch: CharData = [this._terminal.eraseAttr(), ' ', 1]; // xterm
while (param--) {
this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1);
@@ -558,16 +551,14 @@ export class InputHandler implements IInputHandler {
* Erase Ps Character(s) (default = 1) (ECH).
*/
public eraseChars(params: number[]): void {
let param, row, j, ch;
param = params[0];
let param = params[0];
if (param < 1) {
param = 1;
}
row = this._terminal.buffer.y + this._terminal.buffer.ybase;
j = this._terminal.buffer.x;
ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm
const row = this._terminal.buffer.y + this._terminal.buffer.ybase;
let j = this._terminal.buffer.x;
const ch: CharData = [this._terminal.eraseAttr(), ' ', 1]; // xterm
while (param-- && j < this._terminal.cols) {
this._terminal.buffer.lines.get(row)[j++] = ch;
@@ -619,9 +610,9 @@ export class InputHandler implements IInputHandler {
* CSI Ps b Repeat the preceding graphic character Ps times (REP).
*/
public repeatPrecedingCharacter(params: number[]): void {
let param = params[0] || 1
, line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y)
, ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1];
let param = params[0] || 1;
const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y);
const ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1];
while (param--) {
line[this._terminal.buffer.x++] = ch;
@@ -867,7 +858,7 @@ export class InputHandler implements IInputHandler {
this._terminal.insertMode = true;
break;
case 20:
// this._terminal.convertEol = true;
// this._t.convertEol = true;
break;
}
} else if (this._terminal.prefix === '?') {
@@ -954,7 +945,6 @@ export class InputHandler implements IInputHandler {
case 47: // alt screen buffer
case 1047: // alt screen buffer
this._terminal.buffers.activateAltBuffer();
this._terminal.reset();
this._terminal.viewport.syncScrollArea();
this._terminal.showCursor();
break;
@@ -1059,7 +1049,7 @@ export class InputHandler implements IInputHandler {
this._terminal.insertMode = false;
break;
case 20:
// this._terminal.convertEol = false;
// this._t.convertEol = false;
break;
}
} else if (this._terminal.prefix === '?') {
@@ -1203,14 +1193,13 @@ export class InputHandler implements IInputHandler {
return;
}
let l = params.length
, i = 0
, flags = this._terminal.curAttr >> 18
, fg = (this._terminal.curAttr >> 9) & 0x1ff
, bg = this._terminal.curAttr & 0x1ff
, p;
const l = params.length;
let flags = this._terminal.curAttr >> 18;
let fg = (this._terminal.curAttr >> 9) & 0x1ff;
let bg = this._terminal.curAttr & 0x1ff;
let p;
for (; i < l; i++) {
for (let i = 0; i < l; i++) {
p = params[i];
if (p >= 30 && p <= 37) {
// fg color 8
@@ -1470,7 +1459,7 @@ export class InputHandler implements IInputHandler {
}
}
export const wcwidth = (function(opts) {
export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number {
// extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c
// combining characters
const COMBINING_BMP = [
@@ -1526,7 +1515,7 @@ export const wcwidth = (function(opts) {
[0xE0100, 0xE01EF]
];
// binary search
function bisearch(ucs, data) {
function bisearch(ucs: number, data: number[][]): boolean {
let min = 0;
let max = data.length - 1;
let mid;
@@ -1543,7 +1532,7 @@ export const wcwidth = (function(opts) {
}
return false;
}
function wcwidthBMP(ucs) {
function wcwidthBMP(ucs: number): number {
// test for 8-bit control characters
if (ucs === 0)
return opts.nul;
@@ -1558,7 +1547,7 @@ export const wcwidth = (function(opts) {
}
return 1;
}
function isWideBMP(ucs) {
function isWideBMP(ucs: number): boolean {
return (
ucs >= 0x1100 && (
ucs <= 0x115f || // Hangul Jamo init. consonants
@@ -1572,7 +1561,7 @@ export const wcwidth = (function(opts) {
(ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
(ucs >= 0xffe0 && ucs <= 0xffe6)));
}
function wcwidthHigh(ucs) {
function wcwidthHigh(ucs: number): 0 | 1 | 2 {
if (bisearch(ucs, COMBINING_HIGH))
return 0;
if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {
@@ -1581,8 +1570,8 @@ export const wcwidth = (function(opts) {
return 1;
}
const control = opts.control | 0;
let table = null;
function init_table() {
let table: number[] | Uint32Array = null;
function init_table(): 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
@@ -1599,7 +1588,7 @@ export const wcwidth = (function(opts) {
num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos);
table[i] = num;
}
return table;
return table;
}
// get width from lookup table
// position in container : num / CODEPOINTS_PER_ITEM
@@ -1613,7 +1602,7 @@ export const wcwidth = (function(opts) {
// ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766
// we are only interested in 2 LSBs, cut off higher bits
// ==> n = n & 3 e.g. 000000000000000000000000000000XX
return function (num) {
return function (num: number): number {
num = num | 0; // get asm.js like optimization under V8
if (num < 32)
return control | 0;
+116 -16
View File
@@ -2,8 +2,8 @@
* @license MIT
*/
import { LinkMatcherOptions } from './Interfaces';
import { LinkMatcherHandler, LinkMatcherValidationCallback } from './Types';
import { ILinkMatcherOptions } from './Interfaces';
import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset, LineData } from './Types';
export interface IBrowser {
isNode: boolean;
@@ -17,7 +17,7 @@ export interface IBrowser {
isMSWindows: boolean;
}
export interface ITerminal {
export interface ITerminal extends IEventEmitter {
element: HTMLElement;
rowContainer: HTMLElement;
selectionContainer: HTMLElement;
@@ -32,7 +32,7 @@ export interface ITerminal {
cursorHidden: boolean;
cursorState: number;
defAttr: number;
scrollback: number;
options: ITerminalOptions;
buffers: IBufferSet;
buffer: IBuffer;
@@ -40,23 +40,110 @@ export interface ITerminal {
* Emit the 'data' event and populate the given data.
* @param data The data to populate in the event.
*/
handler(data: string);
on(event: string, callback: () => void);
scrollDisp(disp: number, suppressScrollEvent: boolean);
cancel(ev: Event, force?: boolean);
handler(data: string): void;
scrollDisp(disp: number, suppressScrollEvent?: boolean): void;
cancel(ev: Event, force?: boolean): boolean | void;
log(text: string): void;
emit(event: string, data: any);
reset(): void;
showCursor(): void;
blankLine(cur?: boolean, isWrapped?: boolean): LineData;
}
/**
* This interface encapsulates everything needed from the Terminal by the
* InputHandler. This cleanly separates the large amount of methods needed by
* InputHandler cleanly from the ITerminal interface.
*/
export interface IInputHandlingTerminal extends IEventEmitter {
element: HTMLElement;
options: ITerminalOptions;
cols: number;
rows: number;
charset: Charset;
gcharset: number;
glevel: number;
charsets: Charset[];
applicationKeypad: boolean;
applicationCursor: boolean;
originMode: boolean;
insertMode: boolean;
wraparoundMode: boolean;
defAttr: number;
curAttr: number;
prefix: string;
savedCols: number;
x10Mouse: boolean;
vt200Mouse: boolean;
normalMouse: boolean;
mouseEvents: boolean;
sendFocus: boolean;
utfMouse: boolean;
sgrMouse: boolean;
urxvtMouse: boolean;
cursorHidden: boolean;
buffers: IBufferSet;
buffer: IBuffer;
viewport: IViewport;
selectionManager: ISelectionManager;
focus(): void;
convertEol: boolean;
updateRange(y: number): void;
scroll(isWrapped?: boolean): void;
nextStop(x?: number): number;
setgLevel(g: number): void;
eraseAttr(): number;
eraseRight(x: number, y: number): void;
eraseLine(y: number): void;
eraseLeft(x: number, y: number): void;
blankLine(cur?: boolean, isWrapped?: boolean): LineData;
prevStop(x?: number): number;
is(term: string): boolean;
send(data: string): void;
setgCharset(g: number, charset: Charset): void;
resize(x: number, y: number): void;
log(text: string, data?: any): void;
reset(): void;
showCursor(): void;
refresh(start: number, end: number): void;
matchColor(r1: number, g1: number, b1: number): number;
error(text: string, data?: any): void;
setOption(key: string, value: any): void;
}
export interface ITerminalOptions {
cancelEvents?: boolean;
colors?: string[];
cols?: number;
convertEol?: boolean;
cursorBlink?: boolean;
cursorStyle?: string;
debug?: boolean;
disableStdin?: boolean;
geometry?: [number, number];
handler?: (data: string) => void;
popOnBell?: boolean;
rows?: number;
screenKeys?: boolean;
scrollback?: number;
tabStopWidth?: number;
termName?: string;
useFlowControl?: boolean;
visualBell?: boolean;
}
export interface IBuffer {
lines: ICircularList<[number, string, number][]>;
lines: ICircularList<LineData>;
ydisp: number;
ybase: number;
y: number;
x: number;
tabs: any;
scrollBottom: number;
scrollTop: number;
savedY: number;
savedX: number;
}
export interface IBufferSet {
@@ -68,12 +155,19 @@ export interface IBufferSet {
activateAltBuffer(): void;
}
export interface IViewport {
syncScrollArea(): void;
}
export interface ISelectionManager {
selectionText: string;
selectionStart: [number, number];
selectionEnd: [number, number];
setSelection(row: number, col: number, length: number);
disable(): void;
enable(): void;
setBuffer(buffer: ICircularList<LineData>): void;
setSelection(row: number, col: number, length: number): void;
}
export interface ICharMeasure {
@@ -85,15 +179,15 @@ export interface ICharMeasure {
export interface ILinkifier {
linkifyRow(rowIndex: number): void;
attachHypertextLinkHandler(handler: LinkMatcherHandler): void;
registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: LinkMatcherOptions): number;
registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number;
deregisterLinkMatcher(matcherId: number): boolean;
}
export interface ICircularList<T> extends IEventEmitter {
length: number;
maxLength: number;
forEach: (callbackfn: (value: T, index: number) => void) => void;
forEach(callbackfn: (value: T, index: number, array: T[]) => void): void;
get(index: number): T;
set(index: number, value: T): void;
push(value: T): void;
@@ -104,11 +198,17 @@ export interface ICircularList<T> extends IEventEmitter {
}
export interface IEventEmitter {
on(type, listener): void;
off(type, listener): void;
on(type: string, listener: IListenerType): void;
off(type: string, listener: IListenerType): void;
emit(type: string, data?: any): void;
}
export interface LinkMatcherOptions {
export interface IListenerType {
(data?: any): void;
listener?: (data?: any) => void;
};
export interface ILinkMatcherOptions {
/**
* The index of the link from the regex.match(text) call. This defaults to 0
* (for regular expressions without capture groups).
+13 -12
View File
@@ -1,6 +1,7 @@
/**
* @license MIT
*/
import jsdom = require('jsdom');
import { assert } from 'chai';
import { ITerminal, ILinkifier } from './Interfaces';
@@ -32,7 +33,7 @@ describe('Linkifier', () => {
linkifier = new TestLinkifier();
});
function addRow(html: string) {
function addRow(html: string): void {
const element = document.createElement('div');
element.innerHTML = html;
container.appendChild(element);
@@ -57,24 +58,24 @@ describe('Linkifier', () => {
document.body.appendChild(container);
});
function clickElement(element: Node) {
function clickElement(element: Node): void {
const event = document.createEvent('MouseEvent');
event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
element.dispatchEvent(event);
}
function assertLinkifiesEntireRow(uri: string, done: MochaDone) {
addRow(uri);
linkifier.linkifyRow(0);
setTimeout(() => {
assert.equal((<HTMLElement>rows[0].firstChild).tagName, 'A');
assert.equal((<HTMLElement>rows[0].firstChild).textContent, uri);
done();
}, 0);
function assertLinkifiesEntireRow(uri: string, done: MochaDone): void {
addRow(uri);
linkifier.linkifyRow(0);
setTimeout(() => {
assert.equal((<HTMLElement>rows[0].firstChild).tagName, 'A');
assert.equal((<HTMLElement>rows[0].firstChild).textContent, uri);
done();
}, 0);
}
describe('http links', () => {
function assertLinkifiesEntireRow(uri: string, done: MochaDone) {
function assertLinkifiesEntireRow(uri: string, done: MochaDone): void {
addRow(uri);
linkifier.linkifyRow(0);
setTimeout(() => {
@@ -87,7 +88,7 @@ describe('Linkifier', () => {
});
describe('link matcher', () => {
function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, expectedHtml: string, done: MochaDone) {
function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, expectedHtml: string, done: MochaDone): void {
addRow(rowText);
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
linkifier.linkifyRow(0);
+4 -4
View File
@@ -2,7 +2,7 @@
* @license MIT
*/
import { LinkMatcherOptions } from './Interfaces';
import { ILinkMatcherOptions } from './Interfaces';
import { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback } from './Types';
const INVALID_LINK_CLASS = 'xterm-invalid-link';
@@ -60,7 +60,7 @@ export class Linkifier {
* @param document The document object.
* @param rows The array of rows to apply links to.
*/
public attachToDom(document: Document, rows: HTMLElement[]) {
public attachToDom(document: Document, rows: HTMLElement[]): void {
this._document = document;
this._rows = rows;
}
@@ -108,10 +108,10 @@ export class Linkifier {
* this searches the textContent of the rows. You will want to use \s to match
* a space ' ' character for example.
* @param {LinkHandler} handler The callback when the link is called.
* @param {LinkMatcherOptions} [options] Options for the link matcher.
* @param {ILinkMatcherOptions} [options] Options for the link matcher.
* @return {number} The ID of the new matcher, this can be used to deregister.
*/
public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: LinkMatcherOptions = {}): number {
public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: ILinkMatcherOptions = {}): number {
if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) {
throw new Error('handler must be defined');
}
+7 -2
View File
@@ -182,7 +182,12 @@ export class Parser {
* @param data The data to parse.
*/
public parse(data: string): ParserState {
let l = data.length, j, cs, ch, code, low;
const l = data.length;
let j;
let cs;
let ch;
let code;
let low;
if (this._terminal.debug) {
this._terminal.log('data: ' + data);
@@ -608,7 +613,7 @@ export class Parser {
*
* @param param the parameter.
*/
public setParam(param: number) {
public setParam(param: number): void {
this._terminal.currentParam = param;
}
+6 -6
View File
@@ -37,7 +37,7 @@ export class Renderer {
// Figure out whether boldness affects
// the character width of monospace fonts.
if (brokenBold === null) {
brokenBold = checkBoldBroken((<any>this._terminal).element);
brokenBold = checkBoldBroken(this._terminal.element);
}
this._spanElementObjectPool = new DomElementObjectPool('span');
@@ -327,7 +327,7 @@ export class Renderer {
* @param start The selection start.
* @param end The selection end.
*/
public refreshSelection(start: [number, number], end: [number, number]) {
public refreshSelection(start: [number, number], end: [number, number]): void {
// Remove all selections
while (this._terminal.selectionContainer.children.length) {
this._terminal.selectionContainer.removeChild(this._terminal.selectionContainer.children[0]);
@@ -385,16 +385,16 @@ export class Renderer {
// If bold is broken, we can't use it in the terminal.
function checkBoldBroken(terminal) {
const document = terminal.ownerDocument;
function checkBoldBroken(terminalElement: HTMLElement): boolean {
const document = terminalElement.ownerDocument;
const el = document.createElement('span');
el.innerHTML = 'hello world';
terminal.appendChild(el);
terminalElement.appendChild(el);
const w1 = el.offsetWidth;
const h1 = el.offsetHeight;
el.style.fontWeight = 'bold';
const w2 = el.offsetWidth;
const h2 = el.offsetHeight;
terminal.removeChild(el);
terminalElement.removeChild(el);
return w1 !== w2 || h1 !== h2;
}
+22 -16
View File
@@ -1,6 +1,7 @@
/**
* @license MIT
*/
import jsdom = require('jsdom');
import { assert } from 'chai';
import { ITerminal, ICircularList } from './Interfaces';
@@ -9,11 +10,13 @@ import { CircularList } from './utils/CircularList';
import { SelectionManager } from './SelectionManager';
import { SelectionModel } from './SelectionModel';
import { BufferSet } from './BufferSet';
import { MockTerminal } from './utils/TestUtils';
import { LineData } from './Types';
class TestSelectionManager extends SelectionManager {
constructor(
terminal: ITerminal,
buffer: ICircularList<[number, string, number][]>,
buffer: ICircularList<LineData>,
rowContainer: HTMLElement,
charMeasure: CharMeasure
) {
@@ -37,7 +40,7 @@ describe('SelectionManager', () => {
let document: Document;
let terminal: ITerminal;
let bufferLines: ICircularList<[number, string, number][]>;
let bufferLines: ICircularList<LineData>;
let rowContainer: HTMLElement;
let selectionManager: TestSelectionManager;
@@ -46,16 +49,18 @@ describe('SelectionManager', () => {
window = dom.window;
document = window.document;
rowContainer = document.createElement('div');
terminal = <any>{ cols: 80, rows: 2 };
terminal.scrollback = 100;
terminal = new MockTerminal();
terminal.cols = 80;
terminal.rows = 2;
terminal.options.scrollback = 100;
terminal.buffers = new BufferSet(terminal);
terminal.buffer = terminal.buffers.active;
bufferLines = terminal.buffer.lines;
selectionManager = new TestSelectionManager(terminal, bufferLines, rowContainer, null);
});
function stringToRow(text: string): [number, string, number][] {
let result: [number, string, number][] = [];
function stringToRow(text: string): LineData {
let result: LineData = [];
for (let i = 0; i < text.length; i++) {
result.push([0, text.charAt(i), 1]);
}
@@ -64,7 +69,7 @@ describe('SelectionManager', () => {
describe('_selectWordAt', () => {
it('should expand selection for normal width chars', () => {
bufferLines.push(stringToRow('foo bar'));
bufferLines.set(0, stringToRow('foo bar'));
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, 'foo');
selectionManager.selectWordAt([1, 0]);
@@ -81,7 +86,7 @@ describe('SelectionManager', () => {
assert.equal(selectionManager.selectionText, 'bar');
});
it('should expand selection for whitespace', () => {
bufferLines.push(stringToRow('a b'));
bufferLines.set(0, stringToRow('a b'));
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, 'a');
selectionManager.selectWordAt([1, 0]);
@@ -95,7 +100,7 @@ describe('SelectionManager', () => {
});
it('should expand selection for wide characters', () => {
// Wide characters use a special format
bufferLines.push([
bufferLines.set(0, [
[null, '中', 2],
[null, '', 0],
[null, '文', 2],
@@ -147,7 +152,7 @@ describe('SelectionManager', () => {
assert.equal(selectionManager.selectionText, 'foo');
});
it('should select up to non-path characters that are commonly adjacent to paths', () => {
bufferLines.push(stringToRow('(cd)[ef]{gh}\'ij"'));
bufferLines.set(0, stringToRow('(cd)[ef]{gh}\'ij"'));
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, '(cd');
selectionManager.selectWordAt([1, 0]);
@@ -185,7 +190,7 @@ describe('SelectionManager', () => {
describe('_selectLineAt', () => {
it('should select the entire line', () => {
bufferLines.push(stringToRow('foo bar'));
bufferLines.set(0, stringToRow('foo bar'));
selectionManager.selectLineAt(0);
assert.equal(selectionManager.selectionText, 'foo bar', 'The selected text is correct');
assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]);
@@ -195,11 +200,12 @@ describe('SelectionManager', () => {
describe('selectAll', () => {
it('should select the entire buffer, beyond the viewport', () => {
bufferLines.push(stringToRow('1'));
bufferLines.push(stringToRow('2'));
bufferLines.push(stringToRow('3'));
bufferLines.push(stringToRow('4'));
bufferLines.push(stringToRow('5'));
bufferLines.length = 5;
bufferLines.set(0, stringToRow('1'));
bufferLines.set(1, stringToRow('2'));
bufferLines.set(2, stringToRow('3'));
bufferLines.set(3, stringToRow('4'));
bufferLines.set(4, stringToRow('5'));
selectionManager.selectAll();
terminal.buffer.ybase = bufferLines.length - terminal.rows;
assert.equal(selectionManager.selectionText, '1\n2\n3\n4\n5');
+13 -12
View File
@@ -7,9 +7,10 @@ import * as Browser from './utils/Browser';
import { CharMeasure } from './utils/CharMeasure';
import { CircularList } from './utils/CircularList';
import { EventEmitter } from './EventEmitter';
import { ITerminal, ICircularList } from './Interfaces';
import { ITerminal, ICircularList, ISelectionManager } from './Interfaces';
import { SelectionModel } from './SelectionModel';
import { translateBufferLineToString } from './utils/BufferLine';
import { LineData } from './Types';
/**
* The number of pixels the mouse needs to be above or below the viewport in
@@ -66,7 +67,7 @@ enum SelectionMode {
* not handled by the SelectionManager but a 'refresh' event is fired when the
* selection is ready to be redrawn.
*/
export class SelectionManager extends EventEmitter {
export class SelectionManager extends EventEmitter implements ISelectionManager {
protected _model: SelectionModel;
/**
@@ -101,7 +102,7 @@ export class SelectionManager extends EventEmitter {
constructor(
private _terminal: ITerminal,
private _buffer: ICircularList<[number, string, number][]>,
private _buffer: ICircularList<LineData>,
private _rowContainer: HTMLElement,
private _charMeasure: CharMeasure
) {
@@ -116,7 +117,7 @@ export class SelectionManager extends EventEmitter {
/**
* Initializes listener variables.
*/
private _initListeners() {
private _initListeners(): void {
this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event);
this._mouseUpListener = event => this._onMouseUp(<MouseEvent>event);
@@ -133,7 +134,7 @@ export class SelectionManager extends EventEmitter {
* Disables the selection manager. This is useful for when terminal mouse
* are enabled.
*/
public disable() {
public disable(): void {
this.clearSelection();
this._enabled = false;
}
@@ -141,7 +142,7 @@ export class SelectionManager extends EventEmitter {
/**
* Enable the selection manager.
*/
public enable() {
public enable(): void {
this._enabled = true;
}
@@ -150,7 +151,7 @@ export class SelectionManager extends EventEmitter {
* switched in or out.
* @param buffer The active buffer.
*/
public setBuffer(buffer: ICircularList<[number, string, number][]>): void {
public setBuffer(buffer: ICircularList<LineData>): void {
this._buffer = buffer;
this.clearSelection();
}
@@ -267,7 +268,7 @@ export class SelectionManager extends EventEmitter {
* Handle the buffer being trimmed, adjust the selection position.
* @param amount The amount the buffer is being trimmed.
*/
private _onTrim(amount: number) {
private _onTrim(amount: number): void {
const needsRefresh = this._model.onTrim(amount);
if (needsRefresh) {
this.refresh();
@@ -316,7 +317,7 @@ export class SelectionManager extends EventEmitter {
* Handles te mousedown event, setting up for a new selection.
* @param event The mousedown event.
*/
private _onMouseDown(event: MouseEvent) {
private _onMouseDown(event: MouseEvent): void {
// If we have selection, we want the context menu on right click even if the
// terminal is in mouse mode.
if (event.button === 2 && this.hasSelection) {
@@ -455,7 +456,7 @@ export class SelectionManager extends EventEmitter {
* end of the selection and refreshing the selection.
* @param event The mousemove event.
*/
private _onMouseMove(event: MouseEvent) {
private _onMouseMove(event: MouseEvent): void {
// Record the previous position so we know whether to redraw the selection
// at the end.
const previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null;
@@ -511,7 +512,7 @@ export class SelectionManager extends EventEmitter {
* The callback that occurs every DRAG_SCROLL_INTERVAL ms that does the
* scrolling of the viewport.
*/
private _dragScroll() {
private _dragScroll(): void {
if (this._dragScrollAmount) {
this._terminal.scrollDisp(this._dragScrollAmount, false);
// Re-evaluate selection
@@ -528,7 +529,7 @@ export class SelectionManager extends EventEmitter {
* Handles the mouseup event, removing the mousedown listeners.
* @param event The mouseup event.
*/
private _onMouseUp(event: MouseEvent) {
private _onMouseUp(event: MouseEvent): void {
this._removeMouseDownListeners();
}
+6 -2
View File
@@ -1,10 +1,12 @@
/**
* @license MIT
*/
import { assert } from 'chai';
import { ITerminal } from './Interfaces';
import { SelectionModel } from './SelectionModel';
import {BufferSet} from './BufferSet';
import { MockTerminal } from './utils/TestUtils';
class TestSelectionModel extends SelectionModel {
constructor(
@@ -22,8 +24,10 @@ describe('SelectionManager', () => {
let model: TestSelectionModel;
beforeEach(() => {
terminal = <any>{ cols: 80, rows: 2, ybase: 0 };
terminal.scrollback = 10;
terminal = new MockTerminal();
terminal.cols = 80;
terminal.rows = 2;
terminal.options.scrollback = 10;
terminal.buffers = new BufferSet(terminal);
terminal.buffer = terminal.buffers.active;
+2345
View File
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More