diff --git a/src/Buffer.ts b/src/Buffer.ts index b76cdc4d..dc0bc24f 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -2,7 +2,7 @@ * @license MIT */ -import { ITerminal } from './Interfaces'; +import { ITerminal, IBuffer } from './Interfaces'; import { CircularList } from './utils/CircularList'; /** @@ -12,7 +12,7 @@ import { CircularList } from './utils/CircularList'; * - cursor position * - scroll position */ -export class Buffer { +export class Buffer implements IBuffer { public lines: CircularList<[number, string, number][]>; public savedY: number; diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 158c07c9..95624296 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -5,7 +5,8 @@ import { wcwidth } from './InputHandler'; describe('InputHandler', () => { describe('save and restore cursor', () => { let terminal = { buffer: { x: 1, y: 2 } }; - let inputHandler = new InputHandler(terminal); + // TODO: Create proper mock IInputHandlingTerminal test util object + let inputHandler = new InputHandler(terminal); // Save cursor position inputHandler.saveCursor([]); assert.equal(terminal.buffer.x, 1); @@ -24,7 +25,7 @@ describe('InputHandler', () => { let terminal = { setOption: (option, value) => options[option] = value }; - let inputHandler = new InputHandler(terminal); + let inputHandler = new InputHandler(terminal); inputHandler.setCursorStyle([0]); assert.equal(options['cursorStyle'], 'block'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index e119df32..8f566cd9 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -2,7 +2,7 @@ * @license MIT */ -import { IInputHandler, ITerminal } from './Interfaces'; +import { IInputHandler, ITerminal, IInputHandlingTerminal } from './Interfaces'; import { C0 } from './EscapeSequences'; import { DEFAULT_CHARSET } from './Charsets'; @@ -15,7 +15,7 @@ import { DEFAULT_CHARSET } from './Charsets'; */ 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; + (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(); } } @@ -194,7 +194,7 @@ export class InputHandler implements IInputHandler { const row = this._terminal.buffer.y + this._terminal.buffer.ybase; let j = this._terminal.buffer.x; - const ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const ch: [number, string, number] = [this._terminal.eraseAttr(), ' ', 1]; // xterm while (param-- && j < this._terminal.cols) { this._terminal.buffer.lines.get(row).splice(j++, 0, ch); @@ -510,7 +510,7 @@ export class InputHandler implements IInputHandler { } const row = this._terminal.buffer.y + this._terminal.buffer.ybase; - const ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const ch: [number, string, number] = [this._terminal.eraseAttr(), ' ', 1]; // xterm while (param--) { this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1); @@ -558,7 +558,7 @@ export class InputHandler implements IInputHandler { const row = this._terminal.buffer.y + this._terminal.buffer.ybase; let j = this._terminal.buffer.x; - const ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const ch: [number, string, number] = [this._terminal.eraseAttr(), ' ', 1]; // xterm while (param-- && j < this._terminal.cols) { this._terminal.buffer.lines.get(row)[j++] = ch; @@ -858,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 === '?') { @@ -1050,7 +1050,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 === '?') { diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 8851edac..b61c0057 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -3,7 +3,7 @@ */ import { LinkMatcherOptions } from './Interfaces'; -import { LinkMatcherHandler, LinkMatcherValidationCallback } from './Types'; +import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset } 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; @@ -45,11 +45,73 @@ export interface ITerminal { scrollDisp(disp: number, suppressScrollEvent: boolean); cancel(ev: Event, force?: boolean); log(text: string): void; - emit(event: string, data: any); reset(): void; showCursor(): void; } +/** + * 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(): any; + eraseRight(x: number, y: number): void; + eraseLine(y: number): void; + eraseLeft(x: number, y: number): void; + blankLine(cur?: boolean, isWrapped?: boolean): [number, string, number][]; + 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, g1, b1): any; + error(text: string, data?: any): void; + setOption(key: string, value: any): void; +} + export interface ITerminalOptions { cancelEvents?: boolean; colors?: string[]; @@ -78,6 +140,10 @@ export interface IBuffer { y: number; x: number; tabs: any; + scrollBottom: number; + scrollTop: number; + savedY: number; + savedX: number; } export interface IBufferSet { @@ -89,11 +155,18 @@ export interface IBufferSet { activateAltBuffer(): void; } +export interface IViewport { + syncScrollArea(): void; +} + export interface ISelectionManager { selectionText: string; selectionStart: [number, number]; selectionEnd: [number, number]; + disable(): void; + enable(): void; + setBuffer(buffer: ICircularList<[number, string, number][]>): void; setSelection(row: number, col: number, length: number); } @@ -127,6 +200,7 @@ export interface ICircularList extends IEventEmitter { export interface IEventEmitter { on(type, listener): void; off(type, listener): void; + emit(type: string, data?: any): void; } export interface LinkMatcherOptions { diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index f6fb44da..bf594f70 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -133,7 +133,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 +141,7 @@ export class SelectionManager extends EventEmitter { /** * Enable the selection manager. */ - public enable() { + public enable(): void { this._enabled = true; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 6cc8864e..df313ee3 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -30,7 +30,7 @@ import { CHARSETS } from './Charsets'; import { getRawByteCoords } from './utils/Mouse'; import { translateBufferLineToString } from './utils/BufferLine'; import { CustomKeyEventHandler, Charset } from './Types'; -import { ITerminal, IBrowser, ITerminalOptions } from './Interfaces'; +import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal } from './Interfaces'; // Declare for RequireJS in loadAddon declare var define: any; @@ -163,7 +163,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { // focusKeys: false, }; -export class Terminal extends EventEmitter implements ITerminal { +export class Terminal extends EventEmitter implements ITerminal, IInputHandlingTerminal { public textarea: HTMLTextAreaElement; public element: HTMLElement; public rowContainer: HTMLElement; @@ -193,7 +193,7 @@ export class Terminal extends EventEmitter implements ITerminal { // TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options public cursorState: number; public cursorHidden: boolean; - private convertEol: boolean; + public convertEol: boolean; // TODO: This is the data queue for send, improve name and documentation private queue: string; private customKeyEventHandler: CustomKeyEventHandler; @@ -203,36 +203,36 @@ export class Terminal extends EventEmitter implements ITerminal { private cursorBlinkInterval: NodeJS.Timer; // modes - private applicationKeypad: boolean; - private applicationCursor: boolean; - private originMode: boolean; - private insertMode: boolean; - private wraparoundMode: boolean; // defaults: xterm - true, vt100 - false + public applicationKeypad: boolean; + public applicationCursor: boolean; + public originMode: boolean; + public insertMode: boolean; + public wraparoundMode: boolean; // defaults: xterm - true, vt100 - false // charset // The current charset - private charset: Charset; - private gcharset: number; - private glevel: number; - private charsets: Charset[]; + public charset: Charset; + public gcharset: number; + public glevel: number; + public charsets: Charset[]; // mouse properties private decLocator: boolean; // This is unstable and never set - private x10Mouse: boolean; - private vt200Mouse: boolean; + public x10Mouse: boolean; + public vt200Mouse: boolean; private vt300Mouse: boolean; // This is unstable and never set - private normalMouse: boolean; - private mouseEvents: boolean; - private sendFocus: boolean; - private utfMouse: boolean; - private sgrMouse: boolean; - private urxvtMouse: boolean; + public normalMouse: boolean; + public mouseEvents: boolean; + public sendFocus: boolean; + public utfMouse: boolean; + public sgrMouse: boolean; + public urxvtMouse: boolean; // misc public children: HTMLElement[]; private refreshStart: number; private refreshEnd: number; - private savedCols: boolean; + public savedCols: number; // stream private readable: boolean; @@ -274,7 +274,7 @@ export class Terminal extends EventEmitter implements ITerminal { private linkifier: Linkifier; public buffers: BufferSet; public buffer: Buffer; - private viewport: Viewport; + public viewport: Viewport; private compositionHelper: CompositionHelper; public charMeasure: CharMeasure; @@ -440,7 +440,7 @@ export class Terminal extends EventEmitter implements ITerminal { * @param {string} key The option key. * @param {any} value The option value. */ - public setOption(key: string, value: any) { + public setOption(key: string, value: any): void { // TODO: Give value a better type (boolean | string, ...) if (!(key in DEFAULT_OPTIONS)) { throw new Error('No option with key "' + key + '"'); @@ -453,7 +453,7 @@ export class Terminal extends EventEmitter implements ITerminal { msg += `(${this.rows}) is not allowed.`; console.warn(msg); - return false; + return; } if (this.options[key] !== value) { @@ -1167,7 +1167,7 @@ export class Terminal extends EventEmitter implements ITerminal { /** * Display the cursor element */ - public showCursor() { + public showCursor(): void { if (!this.cursorState) { this.cursorState = 1; this.refresh(this.buffer.y, this.buffer.y); @@ -1882,7 +1882,7 @@ export class Terminal extends EventEmitter implements ITerminal { * Send data for handling to the terminal * @param {string} data */ - private send(data) { + public send(data: string): void { if (!this.queue) { setTimeout(() => { this.handler(this.queue); @@ -1909,21 +1909,19 @@ export class Terminal extends EventEmitter implements ITerminal { /** * Log the current state to the console. */ - public log(): void { + public log(text: string, data?: any): void { if (!this.options.debug) return; if (!this.context.console || !this.context.console.log) return; - const args = Array.prototype.slice.call(arguments); - this.context.console.log.apply(this.context.console, args); + this.context.console.log(text, data); } /** * Log the current state as error to the console. */ - public error(): void { + public error(text: string, data?: any): void { if (!this.options.debug) return; if (!this.context.console || !this.context.console.error) return; - const args = Array.prototype.slice.call(arguments); - this.context.console.error.apply(this.context.console, args); + this.context.console.error(text, data); } /** @@ -1932,7 +1930,7 @@ export class Terminal extends EventEmitter implements ITerminal { * @param {number} x The number of columns to resize to. * @param {number} y The number of rows to resize to. */ - public resize(x: number, y: number) { + public resize(x: number, y: number): void { if (isNaN(x) || isNaN(y)) { return; } @@ -2052,7 +2050,7 @@ export class Terminal extends EventEmitter implements ITerminal { * Updates the range of rows to refresh * @param {number} y The number of rows to refresh next. */ - public updateRange(y) { + public updateRange(y: number): void { if (y < this.refreshStart) this.refreshStart = y; if (y > this.refreshEnd) this.refreshEnd = y; // if (y > this.refreshEnd) { @@ -2094,7 +2092,7 @@ export class Terminal extends EventEmitter implements ITerminal { * Move the cursor to the previous tab stop from the given position (default is current). * @param {number} x The position to move the cursor to the previous tab stop. */ - public prevStop(x) { + public prevStop(x?: number): number { if (x == null) x = this.buffer.x; while (!this.buffer.tabs[--x] && x > 0); return x >= this.cols ? this.cols - 1 : x < 0 ? 0 : x; @@ -2104,12 +2102,10 @@ export class Terminal extends EventEmitter implements ITerminal { * Move the cursor one tab stop forward from the given position (default is current). * @param {number} x The position to move the cursor one tab stop forward. */ - public nextStop(x) { + public nextStop(x?: number): number { if (x == null) x = this.buffer.x; while (!this.buffer.tabs[++x] && x < this.cols); - return x >= this.cols - ? this.cols - 1 - : x < 0 ? 0 : x; + return x >= this.cols ? this.cols - 1 : x < 0 ? 0 : x; } /** @@ -2134,7 +2130,7 @@ export class Terminal extends EventEmitter implements ITerminal { * @param {number} x The column from which to start erasing to the start of the line. * @param {number} y The line in which to operate. */ - public eraseLeft(x: number, y: number) { + public eraseLeft(x: number, y: number): void { const line = this.buffer.lines.get(this.buffer.ybase + y); if (!line) { return; @@ -2171,7 +2167,7 @@ export class Terminal extends EventEmitter implements ITerminal { * Erase all content in the given line * @param {number} y The line to erase all of its contents. */ - public eraseLine(y): void { + public eraseLine(y: number): void { this.eraseRight(0, y); } @@ -2180,7 +2176,7 @@ export class Terminal extends EventEmitter implements ITerminal { * @param {boolean} cur First bunch of data for each "blank" character. * @param {boolean} isWrapped Whether the new line is wrapped from the previous line. */ - public blankLine(cur?, isWrapped?: boolean) { + public blankLine(cur?: boolean, isWrapped?: boolean): [number, string, number][] { const attr = cur ? this.eraseAttr() : this.defAttr; const ch = [attr, ' ', 1]; // width defaults to 1 halfwidth character @@ -2209,12 +2205,10 @@ export class Terminal extends EventEmitter implements ITerminal { /** * Evaluate if the current terminal is the given argument. - * @param {object} term The terminal to evaluate + * @param term The terminal name to evaluate */ - private is(term) { - // TODO: Do we need this? - const name = this.options.termName; - return (name + '').indexOf(term) === 0; + public is(term: string): boolean { + return (this.options.termName + '').indexOf(term) === 0; } /** @@ -2294,7 +2288,7 @@ export class Terminal extends EventEmitter implements ITerminal { /** * ESC c Full Reset (RIS). */ - public reset() { + public reset(): void { this.options.rows = this.rows; this.options.cols = this.cols; const customKeyEventHandler = this.customKeyEventHandler; @@ -2329,7 +2323,7 @@ export class Terminal extends EventEmitter implements ITerminal { // Expose to InputHandler // TODO: Revise when truecolor is introduced. - public matchColor(r1, g1, b1) { + public matchColor(r1, g1, b1): any { const hash = (r1 << 16) | (g1 << 8) | b1; if (matchColorCache[hash] != null) { diff --git a/src/Viewport.ts b/src/Viewport.ts index fe276935..bd667edb 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -2,14 +2,14 @@ * @license MIT */ -import { ITerminal } from './Interfaces'; +import { ITerminal, IViewport } from './Interfaces'; import { CharMeasure } from './utils/CharMeasure'; /** * Represents the viewport of a terminal, the visible area within the larger buffer of output. * Logic for the virtual scroll bar is included in this object. */ -export class Viewport { +export class Viewport implements IViewport { private currentRowHeight: number; private lastRecordedBufferLength: number; private lastRecordedViewportHeight: number;