import { IInputHandler, ITerminal } from './Interfaces'; import { C0 } from './EscapeSequences'; export class InputHandler implements IInputHandler { // TODO: We want to type _terminal when it's pulled into TS constructor(private _terminal: any) { } /** * BEL * Bell (Ctrl-G). */ public bell(): void { if (!this._terminal.visualBell) { return; } this._terminal.element.style.borderColor = 'white'; setTimeout(() => this._terminal.element.style.borderColor = '', 10); if (this._terminal.popOnBell) { this._terminal.focus(); } } /** * LF * Line Feed or New Line (NL). (LF is Ctrl-J). */ public lineFeed(): void { if (this._terminal.convertEol) { this._terminal.x = 0; } this._terminal.y++; if (this._terminal.y > this._terminal.scrollBottom) { this._terminal.y--; this._terminal.scroll(); } } /** * CR * Carriage Return (Ctrl-M). */ public carriageReturn(): void { this._terminal.x = 0; } /** * BS * Backspace (Ctrl-H). */ public backspace(): void { if (this._terminal.x > 0) { this._terminal.x--; } } /** * TAB * Horizontal Tab (HT) (Ctrl-I). */ public tab(): void { this._terminal.x = this._terminal.nextStop(); } /** * SO * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the * G1 character set. */ public shiftOut(): void { this._terminal.setgLevel(1); } /** * SI * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0 * character set (the default). */ public shiftIn(): void { this._terminal.setgLevel(0); } /** * CSI Ps A * Cursor Up Ps Times (default = 1) (CUU). */ public cursorUp(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; } this._terminal.y -= param; if (this._terminal.y < 0) { this._terminal.y = 0; } } /** * CSI Ps B * Cursor Down Ps Times (default = 1) (CUD). */ public cursorDown(params: number[]) { let param = params[0]; if (param < 1) { param = 1; } this._terminal.y += param; if (this._terminal.y >= this._terminal.rows) { this._terminal.y = this._terminal.rows - 1; } } /** * CSI Ps C * Cursor Forward Ps Times (default = 1) (CUF). */ public cursorForward(params: number[]) { let param = params[0]; if (param < 1) { param = 1; } this._terminal.x += param; if (this._terminal.x >= this._terminal.cols) { this._terminal.x = this._terminal.cols - 1; } } /** * CSI Ps D * Cursor Backward Ps Times (default = 1) (CUB). */ public cursorBackward(params: number[]) { let param = params[0]; if (param < 1) { param = 1; } this._terminal.x -= param; if (this._terminal.x < 0) { this._terminal.x = 0; } } /** * CSI Ps ; Ps H * Cursor Position [row;column] (default = [1,1]) (CUP). */ public cursorPosition(params: number[]) { let row, col; row = params[0] - 1; if (params.length >= 2) { col = params[1] - 1; } else { col = 0; } if (row < 0) { row = 0; } else if (row >= this._terminal.rows) { row = this._terminal.rows - 1; } if (col < 0) { col = 0; } else if (col >= this._terminal.cols) { col = this._terminal.cols - 1; } this._terminal.x = col; this._terminal.y = row; } /** * CSI Ps J Erase in Display (ED). * Ps = 0 -> Erase Below (default). * Ps = 1 -> Erase Above. * Ps = 2 -> Erase All. * Ps = 3 -> Erase Saved Lines (xterm). * CSI ? Ps J * Erase in Display (DECSED). * Ps = 0 -> Selective Erase Below (default). * Ps = 1 -> Selective Erase Above. * Ps = 2 -> Selective Erase All. */ public eraseInDisplay(params: number[]) { let j; switch (params[0]) { case 0: this._terminal.eraseRight(this._terminal.x, this._terminal.y); j = this._terminal.y + 1; for (; j < this._terminal.rows; j++) { this._terminal.eraseLine(j); } break; case 1: this._terminal.eraseLeft(this._terminal.x, this._terminal.y); j = this._terminal.y; while (j--) { this._terminal.eraseLine(j); } break; case 2: j = this._terminal.rows; while (j--) this._terminal.eraseLine(j); break; case 3: ; // no saved lines break; } } /** * CSI Ps K Erase in Line (EL). * Ps = 0 -> Erase to Right (default). * Ps = 1 -> Erase to Left. * Ps = 2 -> Erase All. * CSI ? Ps K * Erase in Line (DECSEL). * Ps = 0 -> Selective Erase to Right (default). * Ps = 1 -> Selective Erase to Left. * Ps = 2 -> Selective Erase All. */ public eraseInLine(params: number[]) { switch (params[0]) { case 0: this._terminal.eraseRight(this._terminal.x, this._terminal.y); break; case 1: this._terminal.eraseLeft(this._terminal.x, this._terminal.y); break; case 2: this._terminal.eraseLine(this._terminal.y); break; } } /** * CSI Pm m Character Attributes (SGR). * Ps = 0 -> Normal (default). * Ps = 1 -> Bold. * Ps = 4 -> Underlined. * Ps = 5 -> Blink (appears as Bold). * Ps = 7 -> Inverse. * Ps = 8 -> Invisible, i.e., hidden (VT300). * Ps = 2 2 -> Normal (neither bold nor faint). * Ps = 2 4 -> Not underlined. * Ps = 2 5 -> Steady (not blinking). * Ps = 2 7 -> Positive (not inverse). * Ps = 2 8 -> Visible, i.e., not hidden (VT300). * Ps = 3 0 -> Set foreground color to Black. * Ps = 3 1 -> Set foreground color to Red. * Ps = 3 2 -> Set foreground color to Green. * Ps = 3 3 -> Set foreground color to Yellow. * Ps = 3 4 -> Set foreground color to Blue. * Ps = 3 5 -> Set foreground color to Magenta. * Ps = 3 6 -> Set foreground color to Cyan. * Ps = 3 7 -> Set foreground color to White. * Ps = 3 9 -> Set foreground color to default (original). * Ps = 4 0 -> Set background color to Black. * Ps = 4 1 -> Set background color to Red. * Ps = 4 2 -> Set background color to Green. * Ps = 4 3 -> Set background color to Yellow. * Ps = 4 4 -> Set background color to Blue. * Ps = 4 5 -> Set background color to Magenta. * Ps = 4 6 -> Set background color to Cyan. * Ps = 4 7 -> Set background color to White. * Ps = 4 9 -> Set background color to default (original). * * If 16-color support is compiled, the following apply. Assume * that xterm's resources are set so that the ISO color codes are * the first 8 of a set of 16. Then the aixterm colors are the * bright versions of the ISO colors: * Ps = 9 0 -> Set foreground color to Black. * Ps = 9 1 -> Set foreground color to Red. * Ps = 9 2 -> Set foreground color to Green. * Ps = 9 3 -> Set foreground color to Yellow. * Ps = 9 4 -> Set foreground color to Blue. * Ps = 9 5 -> Set foreground color to Magenta. * Ps = 9 6 -> Set foreground color to Cyan. * Ps = 9 7 -> Set foreground color to White. * Ps = 1 0 0 -> Set background color to Black. * Ps = 1 0 1 -> Set background color to Red. * Ps = 1 0 2 -> Set background color to Green. * Ps = 1 0 3 -> Set background color to Yellow. * Ps = 1 0 4 -> Set background color to Blue. * Ps = 1 0 5 -> Set background color to Magenta. * Ps = 1 0 6 -> Set background color to Cyan. * Ps = 1 0 7 -> Set background color to White. * * If xterm is compiled with the 16-color support disabled, it * supports the following, from rxvt: * Ps = 1 0 0 -> Set foreground and background color to * default. * * If 88- or 256-color support is compiled, the following apply. * Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second * Ps. * Ps = 4 8 ; 5 ; Ps -> Set background color to the second * Ps. */ public charAttributes(params: number[]) { // Optimize a single SGR0. if (params.length === 1 && params[0] === 0) { this._terminal.curAttr = this._terminal.defAttr; return; } let l = params.length , i = 0 , flags = this._terminal.curAttr >> 18 , fg = (this._terminal.curAttr >> 9) & 0x1ff , bg = this._terminal.curAttr & 0x1ff , p; for (; i < l; i++) { p = params[i]; if (p >= 30 && p <= 37) { // fg color 8 fg = p - 30; } else if (p >= 40 && p <= 47) { // bg color 8 bg = p - 40; } else if (p >= 90 && p <= 97) { // fg color 16 p += 8; fg = p - 90; } else if (p >= 100 && p <= 107) { // bg color 16 p += 8; 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 = 0; // fg = 0x1ff; // bg = 0x1ff; } else if (p === 1) { // bold text flags |= 1; } else if (p === 4) { // underlined text flags |= 2; } else if (p === 5) { // blink flags |= 4; } else if (p === 7) { // inverse and positive // test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m' flags |= 8; } else if (p === 8) { // invisible flags |= 16; } else if (p === 22) { // not bold flags &= ~1; } else if (p === 24) { // not underlined flags &= ~2; } else if (p === 25) { // not blink flags &= ~4; } else if (p === 27) { // not inverse flags &= ~8; } else if (p === 28) { // not invisible flags &= ~16; } else if (p === 39) { // reset fg fg = (this._terminal.defAttr >> 9) & 0x1ff; } else if (p === 49) { // reset bg bg = this._terminal.defAttr & 0x1ff; } else if (p === 38) { // fg color 256 if (params[i + 1] === 2) { i += 2; fg = this._terminal.matchColor( params[i] & 0xff, params[i + 1] & 0xff, params[i + 2] & 0xff); if (fg === -1) fg = 0x1ff; i += 2; } else if (params[i + 1] === 5) { i += 2; p = params[i] & 0xff; fg = p; } } else if (p === 48) { // bg color 256 if (params[i + 1] === 2) { i += 2; bg = this._terminal.matchColor( params[i] & 0xff, params[i + 1] & 0xff, params[i + 2] & 0xff); if (bg === -1) bg = 0x1ff; i += 2; } else if (params[i + 1] === 5) { i += 2; p = params[i] & 0xff; bg = p; } } else if (p === 100) { // reset fg/bg fg = (this._terminal.defAttr >> 9) & 0x1ff; bg = this._terminal.defAttr & 0x1ff; } else { this._terminal.error('Unknown SGR attribute: %d.', p); } } this._terminal.curAttr = (flags << 18) | (fg << 9) | bg; } /** * CSI Ps n Device Status Report (DSR). * Ps = 5 -> Status Report. Result (``OK'') is * CSI 0 n * Ps = 6 -> Report Cursor Position (CPR) [row;column]. * Result is * CSI r ; c R * CSI ? Ps n * Device Status Report (DSR, DEC-specific). * Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI * ? r ; c R (assumes page is zero). * Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready). * or CSI ? 1 1 n (not ready). * Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked) * or CSI ? 2 1 n (locked). * Ps = 2 6 -> Report Keyboard status as * CSI ? 2 7 ; 1 ; 0 ; 0 n (North American). * The last two parameters apply to VT400 & up, and denote key- * board ready and LK01 respectively. * Ps = 5 3 -> Report Locator status as * CSI ? 5 3 n Locator available, if compiled-in, or * CSI ? 5 0 n No Locator, if not. */ public deviceStatus(params: number[]) { if (!this._terminal.prefix) { switch (params[0]) { case 5: // status report this._terminal.send(C0.ESC + '[0n'); break; case 6: // cursor position this._terminal.send(C0.ESC + '[' + (this._terminal.y + 1) + ';' + (this._terminal.x + 1) + 'R'); break; } } else if (this._terminal.prefix === '?') { // modern xterm doesnt seem to // respond to any of these except ?6, 6, and 5 switch (params[0]) { case 6: // cursor position this._terminal.send(C0.ESC + '[?' + (this._terminal.y + 1) + ';' + (this._terminal.x + 1) + 'R'); break; case 15: // no printer // this.send(C0.ESC + '[?11n'); break; case 25: // dont support user defined keys // this.send(C0.ESC + '[?21n'); break; case 26: // north american keyboard // this.send(C0.ESC + '[?27;1;0;0n'); break; case 53: // no dec locator/mouse // this.send(C0.ESC + '[?50n'); break; } } } }