diff --git a/src/Charsets.ts b/src/Charsets.ts new file mode 100644 index 0000000000..3220f7d5a1 --- /dev/null +++ b/src/Charsets.ts @@ -0,0 +1,67 @@ +/** + * @license MIT + */ + +// TODO: Give CHARSETS a proper type +/** + * 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. + */ +export const CHARSETS: any = {}; + +// DEC Special Character and Line Drawing Set. +// http://vt100.net/docs/vt102-ug/table5-13.html +// A lot of curses apps use this if they see TERM=xterm. +// testing: echo -e '\e(0a\e(B' +// The xterm output sometimes seems to conflict with the +// reference above. xterm seems in line with the reference +// when running vttest however. +// The table below now uses xterm's output from vttest. +CHARSETS.SCLD = { // (0 + '`': '\u25c6', // '◆' + 'a': '\u2592', // '▒' + 'b': '\u0009', // '\t' + 'c': '\u000c', // '\f' + 'd': '\u000d', // '\r' + 'e': '\u000a', // '\n' + 'f': '\u00b0', // '°' + 'g': '\u00b1', // '±' + 'h': '\u2424', // '\u2424' (NL) + 'i': '\u000b', // '\v' + 'j': '\u2518', // '┘' + 'k': '\u2510', // '┐' + 'l': '\u250c', // '┌' + 'm': '\u2514', // '└' + 'n': '\u253c', // '┼' + 'o': '\u23ba', // '⎺' + 'p': '\u23bb', // '⎻' + 'q': '\u2500', // '─' + 'r': '\u23bc', // '⎼' + 's': '\u23bd', // '⎽' + 't': '\u251c', // '├' + 'u': '\u2524', // '┤' + 'v': '\u2534', // '┴' + 'w': '\u252c', // '┬' + 'x': '\u2502', // '│' + 'y': '\u2264', // '≤' + 'z': '\u2265', // '≥' + '{': '\u03c0', // 'π' + '|': '\u2260', // '≠' + '}': '\u00a3', // '£' + '~': '\u00b7' // '·' +}; + +CHARSETS.UK = null; // (A +CHARSETS.US = null; // (B (USASCII) +CHARSETS.Dutch = null; // (4 +CHARSETS.Finnish = null; // (C or (5 +CHARSETS.French = null; // (R +CHARSETS.FrenchCanadian = null; // (Q +CHARSETS.German = null; // (K +CHARSETS.Italian = null; // (Y +CHARSETS.NorwegianDanish = null; // (E or (6 +CHARSETS.Spanish = null; // (Z +CHARSETS.Swedish = null; // (H or (7 +CHARSETS.Swiss = null; // (= +CHARSETS.ISOLatin = null; // /A diff --git a/src/InputHandler.ts b/src/InputHandler.ts new file mode 100644 index 0000000000..00626cebf0 --- /dev/null +++ b/src/InputHandler.ts @@ -0,0 +1,1533 @@ +/** + * @license MIT + */ + +import { IInputHandler, ITerminal } from './Interfaces'; +import { C0 } from './EscapeSequences'; +import { CHARSETS } from './Charsets'; + +/** + * The terminal's standard implementation of IInputHandler, this handles all + * input from the Parser. + * + * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand + * 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) { } + + public addChar(char: string, code: number): void { + if (char >= ' ') { + // calculate print space + // expensive call, therefore we save width in line buffer + const ch_width = wcwidth(code); + + if (this._terminal.charset && this._terminal.charset[char]) { + char = this._terminal.charset[char]; + } + + let row = this._terminal.y + this._terminal.ybase; + + // insert combining char in last cell + // FIXME: needs handling after cursor jumps + if (!ch_width && this._terminal.x) { + // dont overflow left + if (this._terminal.lines.get(row)[this._terminal.x - 1]) { + if (!this._terminal.lines.get(row)[this._terminal.x - 1][2]) { + + // found empty cell after fullwidth, need to go 2 cells back + if (this._terminal.lines.get(row)[this._terminal.x - 2]) + this._terminal.lines.get(row)[this._terminal.x - 2][1] += char; + + } else { + this._terminal.lines.get(row)[this._terminal.x - 1][1] += char; + } + this._terminal.updateRange(this._terminal.y); + } + return; + } + + // goto next line if ch would overflow + // TODO: needs a global min terminal width of 2 + if (this._terminal.x + ch_width - 1 >= this._terminal.cols) { + // autowrap - DECAWM + if (this._terminal.wraparoundMode) { + this._terminal.x = 0; + this._terminal.y++; + if (this._terminal.y > this._terminal.scrollBottom) { + this._terminal.y--; + this._terminal.scroll(); + } + } else { + this._terminal.x = this._terminal.cols - 1; + if (ch_width === 2) // FIXME: check for xterm behavior + return; + } + } + row = this._terminal.y + this._terminal.ybase; + + // insert mode: move characters to right + if (this._terminal.insertMode) { + // do this twice for a fullwidth char + for (let moves = 0; moves < ch_width; ++moves) { + // remove last cell, if it's width is 0 + // we have to adjust the second last cell as well + const removed = this._terminal.lines.get(this._terminal.y + this._terminal.ybase).pop(); + if (removed[2] === 0 + && this._terminal.lines.get(row)[this._terminal.cols - 2] + && this._terminal.lines.get(row)[this._terminal.cols - 2][2] === 2) + this._terminal.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1]; + + // insert empty cell at cursor + this._terminal.lines.get(row).splice(this._terminal.x, 0, [this._terminal.curAttr, ' ', 1]); + } + } + + this._terminal.lines.get(row)[this._terminal.x] = [this._terminal.curAttr, char, ch_width]; + this._terminal.x++; + this._terminal.updateRange(this._terminal.y); + + // fullwidth char - set next cell width to zero and advance cursor + if (ch_width === 2) { + this._terminal.lines.get(row)[this._terminal.x] = [this._terminal.curAttr, '', 0]; + this._terminal.x++; + } + } + } + + /** + * 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 @ + * Insert Ps (Blank) Character(s) (default = 1) (ICH). + */ + public insertChars(params: number[]): void { + let param, row, j, ch; + + param = params[0]; + if (param < 1) param = 1; + + row = this._terminal.y + this._terminal.ybase; + j = this._terminal.x; + ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + + while (param-- && j < this._terminal.cols) { + this._terminal.lines.get(row).splice(j++, 0, ch); + this._terminal.lines.get(row).pop(); + } + } + + /** + * 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 E + * Cursor Next Line Ps Times (default = 1) (CNL). + * same as CSI Ps B ? + */ + public cursorNextLine(params: number[]): void { + 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; + } + this._terminal.x = 0; + }; + + + /** + * CSI Ps F + * Cursor Preceding Line Ps Times (default = 1) (CNL). + * reuse CSI Ps A ? + */ + public cursorPrecedingLine(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; + } + this._terminal.x = 0; + }; + + + /** + * CSI Ps G + * Cursor Character Absolute [column] (default = [row,1]) (CHA). + */ + public cursorCharAbsolute(params: number[]): void { + let param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.x = param - 1; + } + + /** + * CSI Ps ; Ps H + * Cursor Position [row;column] (default = [1,1]) (CUP). + */ + public cursorPosition(params: number[]): void { + 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 I + * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). + */ + public cursorForwardTab(params: number[]): void { + let param = params[0] || 1; + while (param--) { + this._terminal.x = this._terminal.nextStop(); + } + } + + /** + * 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[]): void { + 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[]): void { + 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 Ps L + * Insert Ps Line(s) (default = 1) (IL). + */ + public insertLines(params: number[]): void { + let param, row, j; + + param = params[0]; + if (param < 1) { + param = 1; + } + row = this._terminal.y + this._terminal.ybase; + + j = this._terminal.rows - 1 - this._terminal.scrollBottom; + j = this._terminal.rows - 1 + this._terminal.ybase - j + 1; + + while (param--) { + if (this._terminal.lines.length === this._terminal.lines.maxLength) { + // Trim the start of lines to make room for the new line + this._terminal.lines.trimStart(1); + this._terminal.ybase--; + this._terminal.ydisp--; + row--; + j--; + } + // test: echo -e '\e[44m\e[1L\e[0m' + // blankLine(true) - xterm/linux behavior + this._terminal.lines.splice(row, 0, this._terminal.blankLine(true)); + this._terminal.lines.splice(j, 1); + } + + // this.maxRange(); + this._terminal.updateRange(this._terminal.y); + this._terminal.updateRange(this._terminal.scrollBottom); + } + + /** + * CSI Ps M + * Delete Ps Line(s) (default = 1) (DL). + */ + public deleteLines(params: number[]): void { + let param, row, j; + + param = params[0]; + if (param < 1) { + param = 1; + } + row = this._terminal.y + this._terminal.ybase; + + j = this._terminal.rows - 1 - this._terminal.scrollBottom; + j = this._terminal.rows - 1 + this._terminal.ybase - j; + + while (param--) { + if (this._terminal.lines.length === this._terminal.lines.maxLength) { + // Trim the start of lines to make room for the new line + this._terminal.lines.trimStart(1); + this._terminal.ybase -= 1; + this._terminal.ydisp -= 1; + } + // test: echo -e '\e[44m\e[1M\e[0m' + // blankLine(true) - xterm/linux behavior + this._terminal.lines.splice(j + 1, 0, this._terminal.blankLine(true)); + this._terminal.lines.splice(row, 1); + } + + // this.maxRange(); + this._terminal.updateRange(this._terminal.y); + this._terminal.updateRange(this._terminal.scrollBottom); + } + + /** + * CSI Ps P + * Delete Ps Character(s) (default = 1) (DCH). + */ + public deleteChars(params: number[]): void { + let param, row, ch; + + param = params[0]; + if (param < 1) { + param = 1; + } + + row = this._terminal.y + this._terminal.ybase; + ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + + while (param--) { + this._terminal.lines.get(row).splice(this._terminal.x, 1); + this._terminal.lines.get(row).push(ch); + } + } + + /** + * CSI Ps S Scroll up Ps lines (default = 1) (SU). + */ + public scrollUp(params: number[]): void { + let param = params[0] || 1; + while (param--) { + this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollTop, 1); + this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollBottom, 0, this._terminal.blankLine()); + } + // this.maxRange(); + this._terminal.updateRange(this._terminal.scrollTop); + this._terminal.updateRange(this._terminal.scrollBottom); + } + + /** + * CSI Ps T Scroll down Ps lines (default = 1) (SD). + */ + public scrollDown(params: number[]): void { + let param = params[0] || 1; + while (param--) { + this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollBottom, 1); + this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollTop, 0, this._terminal.blankLine()); + } + // this.maxRange(); + this._terminal.updateRange(this._terminal.scrollTop); + this._terminal.updateRange(this._terminal.scrollBottom); + } + + /** + * CSI Ps X + * Erase Ps Character(s) (default = 1) (ECH). + */ + public eraseChars(params: number[]): void { + let param, row, j, ch; + + param = params[0]; + if (param < 1) { + param = 1; + } + + row = this._terminal.y + this._terminal.ybase; + j = this._terminal.x; + ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + + while (param-- && j < this._terminal.cols) { + this._terminal.lines.get(row)[j++] = ch; + } + } + + /** + * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). + */ + public cursorBackwardTab(params: number[]): void { + let param = params[0] || 1; + while (param--) { + this._terminal.x = this._terminal.prevStop(); + } + } + + /** + * CSI Pm ` Character Position Absolute + * [column] (default = [row,1]) (HPA). + */ + public charPosAbsolute(params: number[]): void { + let param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.x = param - 1; + if (this._terminal.x >= this._terminal.cols) { + this._terminal.x = this._terminal.cols - 1; + } + } + + /** + * CSI Pm a Character Position Relative + * [columns] (default = [row,col+1]) (HPR) + * reuse CSI Ps C ? + */ + public HPositionRelative(params: number[]): void { + 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 b Repeat the preceding graphic character Ps times (REP). + */ + public repeatPrecedingCharacter(params: number[]): void { + let param = params[0] || 1 + , line = this._terminal.lines.get(this._terminal.ybase + this._terminal.y) + , ch = line[this._terminal.x - 1] || [this._terminal.defAttr, ' ', 1]; + + while (param--) { + line[this._terminal.x++] = ch; + } + } + + /** + * CSI Ps c Send Device Attributes (Primary DA). + * Ps = 0 or omitted -> request attributes from terminal. The + * response depends on the decTerminalID resource setting. + * -> CSI ? 1 ; 2 c (``VT100 with Advanced Video Option'') + * -> CSI ? 1 ; 0 c (``VT101 with No Options'') + * -> CSI ? 6 c (``VT102'') + * -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c (``VT220'') + * The VT100-style response parameters do not mean anything by + * themselves. VT220 parameters do, telling the host what fea- + * tures the terminal supports: + * Ps = 1 -> 132-columns. + * Ps = 2 -> Printer. + * Ps = 6 -> Selective erase. + * Ps = 8 -> User-defined keys. + * Ps = 9 -> National replacement character sets. + * Ps = 1 5 -> Technical characters. + * Ps = 2 2 -> ANSI color, e.g., VT525. + * Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode). + * CSI > Ps c + * Send Device Attributes (Secondary DA). + * Ps = 0 or omitted -> request the terminal's identification + * code. The response depends on the decTerminalID resource set- + * ting. It should apply only to VT220 and up, but xterm extends + * this to VT100. + * -> CSI > Pp ; Pv ; Pc c + * where Pp denotes the terminal type + * Pp = 0 -> ``VT100''. + * Pp = 1 -> ``VT220''. + * and Pv is the firmware version (for xterm, this was originally + * the XFree86 patch number, starting with 95). In a DEC termi- + * nal, Pc indicates the ROM cartridge registration number and is + * always zero. + * More information: + * xterm/charproc.c - line 2012, for more information. + * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?) + */ + public sendDeviceAttributes(params: number[]): void { + if (params[0] > 0) { + return; + } + + if (!this._terminal.prefix) { + if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) { + this._terminal.send(C0.ESC + '[?1;2c'); + } else if (this._terminal.is('linux')) { + this._terminal.send(C0.ESC + '[?6c'); + } + } else if (this._terminal.prefix === '>') { + // xterm and urxvt + // seem to spit this + // out around ~370 times (?). + if (this._terminal.is('xterm')) { + this._terminal.send(C0.ESC + '[>0;276;0c'); + } else if (this._terminal.is('rxvt-unicode')) { + this._terminal.send(C0.ESC + '[>85;95;0c'); + } else if (this._terminal.is('linux')) { + // not supported by linux console. + // linux console echoes parameters. + this._terminal.send(params[0] + 'c'); + } else if (this._terminal.is('screen')) { + this._terminal.send(C0.ESC + '[>83;40003;0c'); + } + } + } + + /** + * CSI Pm d Vertical Position Absolute (VPA) + * [row] (default = [1,column]) + */ + public linePosAbsolute(params: number[]): void { + let param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.y = param - 1; + if (this._terminal.y >= this._terminal.rows) { + this._terminal.y = this._terminal.rows - 1; + } + } + + /** + * CSI Pm e Vertical Position Relative (VPR) + * [rows] (default = [row+1,column]) + * reuse CSI Ps B ? + */ + public VPositionRelative(params: number[]): void { + 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 ; Ps f + * Horizontal and Vertical Position [row;column] (default = + * [1,1]) (HVP). + */ + public HVPosition(params: number[]): void { + if (params[0] < 1) params[0] = 1; + if (params[1] < 1) params[1] = 1; + + this._terminal.y = params[0] - 1; + if (this._terminal.y >= this._terminal.rows) { + this._terminal.y = this._terminal.rows - 1; + } + + this._terminal.x = params[1] - 1; + if (this._terminal.x >= this._terminal.cols) { + this._terminal.x = this._terminal.cols - 1; + } + } + + /** + * CSI Ps g Tab Clear (TBC). + * Ps = 0 -> Clear Current Column (default). + * Ps = 3 -> Clear All. + * Potentially: + * Ps = 2 -> Clear Stops on Line. + * http://vt100.net/annarbor/aaa-ug/section6.html + */ + public tabClear(params: number[]): void { + let param = params[0]; + if (param <= 0) { + delete this._terminal.tabs[this._terminal.x]; + } else if (param === 3) { + this._terminal.tabs = {}; + } + } + + /** + * CSI Pm h Set Mode (SM). + * Ps = 2 -> Keyboard Action Mode (AM). + * Ps = 4 -> Insert Mode (IRM). + * Ps = 1 2 -> Send/receive (SRM). + * Ps = 2 0 -> Automatic Newline (LNM). + * CSI ? Pm h + * DEC Private Mode Set (DECSET). + * Ps = 1 -> Application Cursor Keys (DECCKM). + * Ps = 2 -> Designate USASCII for character sets G0-G3 + * (DECANM), and set VT100 mode. + * Ps = 3 -> 132 Column Mode (DECCOLM). + * Ps = 4 -> Smooth (Slow) Scroll (DECSCLM). + * Ps = 5 -> Reverse Video (DECSCNM). + * Ps = 6 -> Origin Mode (DECOM). + * Ps = 7 -> Wraparound Mode (DECAWM). + * Ps = 8 -> Auto-repeat Keys (DECARM). + * Ps = 9 -> Send Mouse X & Y on button press. See the sec- + * tion Mouse Tracking. + * Ps = 1 0 -> Show toolbar (rxvt). + * Ps = 1 2 -> Start Blinking Cursor (att610). + * Ps = 1 8 -> Print form feed (DECPFF). + * Ps = 1 9 -> Set print extent to full screen (DECPEX). + * Ps = 2 5 -> Show Cursor (DECTCEM). + * Ps = 3 0 -> Show scrollbar (rxvt). + * Ps = 3 5 -> Enable font-shifting functions (rxvt). + * Ps = 3 8 -> Enter Tektronix Mode (DECTEK). + * Ps = 4 0 -> Allow 80 -> 132 Mode. + * Ps = 4 1 -> more(1) fix (see curses resource). + * Ps = 4 2 -> Enable Nation Replacement Character sets (DECN- + * RCM). + * Ps = 4 4 -> Turn On Margin Bell. + * Ps = 4 5 -> Reverse-wraparound Mode. + * Ps = 4 6 -> Start Logging. This is normally disabled by a + * compile-time option. + * Ps = 4 7 -> Use Alternate Screen Buffer. (This may be dis- + * abled by the titeInhibit resource). + * Ps = 6 6 -> Application keypad (DECNKM). + * Ps = 6 7 -> Backarrow key sends backspace (DECBKM). + * Ps = 1 0 0 0 -> Send Mouse X & Y on button press and + * release. See the section Mouse Tracking. + * Ps = 1 0 0 1 -> Use Hilite Mouse Tracking. + * Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking. + * Ps = 1 0 0 3 -> Use All Motion Mouse Tracking. + * Ps = 1 0 0 4 -> Send FocusIn/FocusOut events. + * Ps = 1 0 0 5 -> Enable Extended Mouse Mode. + * Ps = 1 0 1 0 -> Scroll to bottom on tty output (rxvt). + * Ps = 1 0 1 1 -> Scroll to bottom on key press (rxvt). + * Ps = 1 0 3 4 -> Interpret "meta" key, sets eighth bit. + * (enables the eightBitInput resource). + * Ps = 1 0 3 5 -> Enable special modifiers for Alt and Num- + * Lock keys. (This enables the numLock resource). + * Ps = 1 0 3 6 -> Send ESC when Meta modifies a key. (This + * enables the metaSendsEscape resource). + * Ps = 1 0 3 7 -> Send DEL from the editing-keypad Delete + * key. + * Ps = 1 0 3 9 -> Send ESC when Alt modifies a key. (This + * enables the altSendsEscape resource). + * Ps = 1 0 4 0 -> Keep selection even if not highlighted. + * (This enables the keepSelection resource). + * Ps = 1 0 4 1 -> Use the CLIPBOARD selection. (This enables + * the selectToClipboard resource). + * Ps = 1 0 4 2 -> Enable Urgency window manager hint when + * Control-G is received. (This enables the bellIsUrgent + * resource). + * Ps = 1 0 4 3 -> Enable raising of the window when Control-G + * is received. (enables the popOnBell resource). + * Ps = 1 0 4 7 -> Use Alternate Screen Buffer. (This may be + * disabled by the titeInhibit resource). + * Ps = 1 0 4 8 -> Save cursor as in DECSC. (This may be dis- + * abled by the titeInhibit resource). + * Ps = 1 0 4 9 -> Save cursor as in DECSC and use Alternate + * Screen Buffer, clearing it first. (This may be disabled by + * the titeInhibit resource). This combines the effects of the 1 + * 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based + * applications rather than the 4 7 mode. + * Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode. + * Ps = 1 0 5 1 -> Set Sun function-key mode. + * Ps = 1 0 5 2 -> Set HP function-key mode. + * Ps = 1 0 5 3 -> Set SCO function-key mode. + * Ps = 1 0 6 0 -> Set legacy keyboard emulation (X11R6). + * Ps = 1 0 6 1 -> Set VT220 keyboard emulation. + * Ps = 2 0 0 4 -> Set bracketed paste mode. + * Modes: + * http: *vt100.net/docs/vt220-rm/chapter4.html + */ + public setMode(params: number[]): void { + if (params.length > 1) { + for (let i = 0; i < params.length; i++) { + this._terminal.setMode(params[i]); + } + + return; + } + + if (!this._terminal.prefix) { + switch (params[0]) { + case 4: + this._terminal.insertMode = true; + break; + case 20: + // this._terminal.convertEol = true; + break; + } + } else if (this._terminal.prefix === '?') { + switch (params[0]) { + case 1: + this._terminal.applicationCursor = true; + break; + case 2: + this._terminal.setgCharset(0, CHARSETS.US); + this._terminal.setgCharset(1, CHARSETS.US); + this._terminal.setgCharset(2, CHARSETS.US); + this._terminal.setgCharset(3, CHARSETS.US); + // set VT100 mode here + break; + case 3: // 132 col mode + this._terminal.savedCols = this._terminal.cols; + this._terminal.resize(132, this._terminal.rows); + break; + case 6: + this._terminal.originMode = true; + break; + case 7: + this._terminal.wraparoundMode = true; + break; + case 12: + // this.cursorBlink = true; + break; + case 66: + this._terminal.log('Serial port requested application keypad.'); + this._terminal.applicationKeypad = true; + this._terminal.viewport.syncScrollArea(); + break; + case 9: // X10 Mouse + // no release, no motion, no wheel, no modifiers. + case 1000: // vt200 mouse + // no motion. + // no modifiers, except control on the wheel. + case 1002: // button event mouse + case 1003: // any event mouse + // any event - sends motion events, + // even if there is no button held down. + + // TODO: Why are params[0] compares nested within a switch for params[0]? + + this._terminal.x10Mouse = params[0] === 9; + this._terminal.vt200Mouse = params[0] === 1000; + this._terminal.normalMouse = params[0] > 1000; + this._terminal.mouseEvents = true; + this._terminal.element.style.cursor = 'default'; + this._terminal.log('Binding to mouse events.'); + break; + case 1004: // send focusin/focusout events + // focusin: ^[[I + // focusout: ^[[O + this._terminal.sendFocus = true; + break; + case 1005: // utf8 ext mode mouse + this._terminal.utfMouse = true; + // for wide terminals + // simply encodes large values as utf8 characters + break; + case 1006: // sgr ext mode mouse + this._terminal.sgrMouse = true; + // for wide terminals + // does not add 32 to fields + // press: ^[[ Keyboard Action Mode (AM). + * Ps = 4 -> Replace Mode (IRM). + * Ps = 1 2 -> Send/receive (SRM). + * Ps = 2 0 -> Normal Linefeed (LNM). + * CSI ? Pm l + * DEC Private Mode Reset (DECRST). + * Ps = 1 -> Normal Cursor Keys (DECCKM). + * Ps = 2 -> Designate VT52 mode (DECANM). + * Ps = 3 -> 80 Column Mode (DECCOLM). + * Ps = 4 -> Jump (Fast) Scroll (DECSCLM). + * Ps = 5 -> Normal Video (DECSCNM). + * Ps = 6 -> Normal Cursor Mode (DECOM). + * Ps = 7 -> No Wraparound Mode (DECAWM). + * Ps = 8 -> No Auto-repeat Keys (DECARM). + * Ps = 9 -> Don't send Mouse X & Y on button press. + * Ps = 1 0 -> Hide toolbar (rxvt). + * Ps = 1 2 -> Stop Blinking Cursor (att610). + * Ps = 1 8 -> Don't print form feed (DECPFF). + * Ps = 1 9 -> Limit print to scrolling region (DECPEX). + * Ps = 2 5 -> Hide Cursor (DECTCEM). + * Ps = 3 0 -> Don't show scrollbar (rxvt). + * Ps = 3 5 -> Disable font-shifting functions (rxvt). + * Ps = 4 0 -> Disallow 80 -> 132 Mode. + * Ps = 4 1 -> No more(1) fix (see curses resource). + * Ps = 4 2 -> Disable Nation Replacement Character sets (DEC- + * NRCM). + * Ps = 4 4 -> Turn Off Margin Bell. + * Ps = 4 5 -> No Reverse-wraparound Mode. + * Ps = 4 6 -> Stop Logging. (This is normally disabled by a + * compile-time option). + * Ps = 4 7 -> Use Normal Screen Buffer. + * Ps = 6 6 -> Numeric keypad (DECNKM). + * Ps = 6 7 -> Backarrow key sends delete (DECBKM). + * Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and + * release. See the section Mouse Tracking. + * Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking. + * Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking. + * Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking. + * Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events. + * Ps = 1 0 0 5 -> Disable Extended Mouse Mode. + * Ps = 1 0 1 0 -> Don't scroll to bottom on tty output + * (rxvt). + * Ps = 1 0 1 1 -> Don't scroll to bottom on key press (rxvt). + * Ps = 1 0 3 4 -> Don't interpret "meta" key. (This disables + * the eightBitInput resource). + * Ps = 1 0 3 5 -> Disable special modifiers for Alt and Num- + * Lock keys. (This disables the numLock resource). + * Ps = 1 0 3 6 -> Don't send ESC when Meta modifies a key. + * (This disables the metaSendsEscape resource). + * Ps = 1 0 3 7 -> Send VT220 Remove from the editing-keypad + * Delete key. + * Ps = 1 0 3 9 -> Don't send ESC when Alt modifies a key. + * (This disables the altSendsEscape resource). + * Ps = 1 0 4 0 -> Do not keep selection when not highlighted. + * (This disables the keepSelection resource). + * Ps = 1 0 4 1 -> Use the PRIMARY selection. (This disables + * the selectToClipboard resource). + * Ps = 1 0 4 2 -> Disable Urgency window manager hint when + * Control-G is received. (This disables the bellIsUrgent + * resource). + * Ps = 1 0 4 3 -> Disable raising of the window when Control- + * G is received. (This disables the popOnBell resource). + * Ps = 1 0 4 7 -> Use Normal Screen Buffer, clearing screen + * first if in the Alternate Screen. (This may be disabled by + * the titeInhibit resource). + * Ps = 1 0 4 8 -> Restore cursor as in DECRC. (This may be + * disabled by the titeInhibit resource). + * Ps = 1 0 4 9 -> Use Normal Screen Buffer and restore cursor + * as in DECRC. (This may be disabled by the titeInhibit + * resource). This combines the effects of the 1 0 4 7 and 1 0 + * 4 8 modes. Use this with terminfo-based applications rather + * than the 4 7 mode. + * Ps = 1 0 5 0 -> Reset terminfo/termcap function-key mode. + * Ps = 1 0 5 1 -> Reset Sun function-key mode. + * Ps = 1 0 5 2 -> Reset HP function-key mode. + * Ps = 1 0 5 3 -> Reset SCO function-key mode. + * Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6). + * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style. + * Ps = 2 0 0 4 -> Reset bracketed paste mode. + */ + public resetMode(params: number[]): void { + if (params.length > 1) { + for (let i = 0; i < params.length; i++) { + this._terminal.resetMode(params[i]); + } + + return; + } + + if (!this._terminal.prefix) { + switch (params[0]) { + case 4: + this._terminal.insertMode = false; + break; + case 20: + // this._terminal.convertEol = false; + break; + } + } else if (this._terminal.prefix === '?') { + switch (params[0]) { + case 1: + this._terminal.applicationCursor = false; + break; + case 3: + if (this._terminal.cols === 132 && this._terminal.savedCols) { + this._terminal.resize(this._terminal.savedCols, this._terminal.rows); + } + delete this._terminal.savedCols; + break; + case 6: + this._terminal.originMode = false; + break; + case 7: + this._terminal.wraparoundMode = false; + break; + case 12: + // this.cursorBlink = false; + break; + case 66: + this._terminal.log('Switching back to normal keypad.'); + this._terminal.applicationKeypad = false; + this._terminal.viewport.syncScrollArea(); + break; + case 9: // X10 Mouse + case 1000: // vt200 mouse + case 1002: // button event mouse + case 1003: // any event mouse + this._terminal.x10Mouse = false; + this._terminal.vt200Mouse = false; + this._terminal.normalMouse = false; + this._terminal.mouseEvents = false; + this._terminal.element.style.cursor = ''; + break; + case 1004: // send focusin/focusout events + this._terminal.sendFocus = false; + break; + case 1005: // utf8 ext mode mouse + this._terminal.utfMouse = false; + break; + case 1006: // sgr ext mode mouse + this._terminal.sgrMouse = false; + break; + case 1015: // urxvt ext mode mouse + this._terminal.urxvtMouse = false; + break; + case 25: // hide cursor + this._terminal.cursorHidden = true; + break; + case 1049: // alt screen buffer cursor + ; // FALL-THROUGH + case 47: // normal screen buffer + case 1047: // normal screen buffer - clearing it first + if (this._terminal.normal) { + this._terminal.lines = this._terminal.normal.lines; + this._terminal.ybase = this._terminal.normal.ybase; + this._terminal.ydisp = this._terminal.normal.ydisp; + this._terminal.x = this._terminal.normal.x; + this._terminal.y = this._terminal.normal.y; + this._terminal.scrollTop = this._terminal.normal.scrollTop; + this._terminal.scrollBottom = this._terminal.normal.scrollBottom; + this._terminal.tabs = this._terminal.normal.tabs; + this._terminal.normal = null; + // if (params === 1049) { + // this.x = this.savedX; + // this.y = this.savedY; + // } + this._terminal.queueRefresh(0, this._terminal.rows - 1); + this._terminal.viewport.syncScrollArea(); + this._terminal.showCursor(); + } + 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[]): void { + // 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[]): void { + 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; + } + } + } + + /** + * CSI ! p Soft terminal reset (DECSTR). + * http://vt100.net/docs/vt220-rm/table4-10.html + */ + public softReset(params: number[]): void { + this._terminal.cursorHidden = false; + this._terminal.insertMode = false; + this._terminal.originMode = false; + this._terminal.wraparoundMode = false; // autowrap + this._terminal.applicationKeypad = false; // ? + this._terminal.viewport.syncScrollArea(); + this._terminal.applicationCursor = false; + this._terminal.scrollTop = 0; + this._terminal.scrollBottom = this._terminal.rows - 1; + this._terminal.curAttr = this._terminal.defAttr; + this._terminal.x = this._terminal.y = 0; // ? + this._terminal.charset = null; + this._terminal.glevel = 0; // ?? + this._terminal.charsets = [null]; // ?? + } + + /** + * CSI Ps ; Ps r + * Set Scrolling Region [top;bottom] (default = full size of win- + * dow) (DECSTBM). + * CSI ? Pm r + */ + public setScrollRegion(params: number[]): void { + if (this._terminal.prefix) return; + this._terminal.scrollTop = (params[0] || 1) - 1; + this._terminal.scrollBottom = (params[1] || this._terminal.rows) - 1; + this._terminal.x = 0; + this._terminal.y = 0; + } + + + /** + * CSI s + * Save cursor (ANSI.SYS). + */ + public saveCursor(params: number[]): void { + this._terminal.savedX = this._terminal.x; + this._terminal.savedY = this._terminal.y; + } + + + /** + * CSI u + * Restore cursor (ANSI.SYS). + */ + public restoreCursor(params: number[]): void { + this._terminal.x = this._terminal.savedX || 0; + this._terminal.y = this._terminal.savedY || 0; + } +} + +const wcwidth = (function(opts) { + // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c + // combining characters + const COMBINING = [ + [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], + [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], + [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], + [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], + [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], + [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], + [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], + [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], + [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], + [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], + [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], + [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], + [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], + [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], + [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], + [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], + [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], + [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], + [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], + [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], + [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], + [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], + [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], + [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], + [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], + [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], + [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], + [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], + [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], + [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], + [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], + [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], + [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], + [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], + [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], + [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], + [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB], + [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], + [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] + ]; + // binary search + function bisearch(ucs) { + let min = 0; + let max = COMBINING.length - 1; + let mid; + if (ucs < COMBINING[0][0] || ucs > COMBINING[max][1]) + return false; + while (max >= min) { + mid = Math.floor((min + max) / 2); + if (ucs > COMBINING[mid][1]) + min = mid + 1; + else if (ucs < COMBINING[mid][0]) + max = mid - 1; + else + return true; + } + 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 isWide(ucs) { + return ( + ucs >= 0x1100 && ( + ucs <= 0x115f || // Hangul Jamo init. consonants + ucs === 0x2329 || + ucs === 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi + (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables + (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs + (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms + (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms + (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms + (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd))); + } + return wcwidth; +})({nul: 0, control: 0}); // configurable options diff --git a/src/Interfaces.ts b/src/Interfaces.ts index ff7bbef2cc..8780928662 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -32,3 +32,54 @@ export interface ITerminal { scrollDisp(disp: number, suppressScrollEvent: boolean); cancel(ev: Event, force?: boolean); } + +/** + * Handles actions generated by the parser. + */ +export interface IInputHandler { + addChar(char: string, code: number): void; + + /** C0 BEL */ bell(): void; + /** C0 LF */ lineFeed(): void; + /** C0 CR */ carriageReturn(): void; + /** C0 BS */ backspace(): void; + /** C0 HT */ tab(): void; + /** C0 SO */ shiftOut(): void; + /** C0 SI */ shiftIn(): void; + + /** CSI @ */ insertChars(params?: number[]): void; + /** CSI A */ cursorUp(params?: number[]): void; + /** CSI B */ cursorDown(params?: number[]): void; + /** CSI C */ cursorForward(params?: number[]): void; + /** CSI D */ cursorBackward(params?: number[]): void; + /** CSI E */ cursorNextLine(params?: number[]): void; + /** CSI F */ cursorPrecedingLine(params?: number[]): void; + /** CSI G */ cursorCharAbsolute(params?: number[]): void; + /** CSI H */ cursorPosition(params?: number[]): void; + /** CSI I */ cursorForwardTab(params?: number[]): void; + /** CSI J */ eraseInDisplay(params?: number[]): void; + /** CSI K */ eraseInLine(params?: number[]): void; + /** CSI L */ insertLines(params?: number[]): void; + /** CSI M */ deleteLines(params?: number[]): void; + /** CSI P */ deleteChars(params?: number[]): void; + /** CSI S */ scrollUp(params?: number[]): void; + /** CSI T */ scrollDown(params?: number[]): void; + /** CSI X */ eraseChars(params?: number[]): void; + /** CSI Z */ cursorBackwardTab(params?: number[]): void; + /** CSI ` */ charPosAbsolute(params?: number[]): void; + /** CSI a */ HPositionRelative(params?: number[]): void; + /** CSI b */ repeatPrecedingCharacter(params?: number[]): void; + /** CSI c */ sendDeviceAttributes(params?: number[]): void; + /** CSI d */ linePosAbsolute(params?: number[]): void; + /** CSI e */ VPositionRelative(params?: number[]): void; + /** CSI f */ HVPosition(params?: number[]): void; + /** CSI g */ tabClear(params?: number[]): void; + /** CSI h */ setMode(params?: number[]): void; + /** CSI l */ resetMode(params?: number[]): void; + /** CSI m */ charAttributes(params?: number[]): void; + /** CSI n */ deviceStatus(params?: number[]): void; + /** CSI p */ softReset(params?: number[]): void; + /** CSI r */ setScrollRegion(params?: number[]): void; + /** CSI s */ saveCursor(params?: number[]): void; + /** CSI u */ restoreCursor(params?: number[]): void; +} diff --git a/src/Parser.ts b/src/Parser.ts new file mode 100644 index 0000000000..860baaa6a6 --- /dev/null +++ b/src/Parser.ts @@ -0,0 +1,650 @@ +/** + * @license MIT + */ + +import { C0 } from './EscapeSequences'; +import { IInputHandler } from './Interfaces'; +import { CHARSETS } from './Charsets'; + +const normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {}; +normalStateHandler[C0.BEL] = (parser, handler) => handler.bell(); +normalStateHandler[C0.LF] = (parser, handler) => handler.lineFeed(); +normalStateHandler[C0.VT] = normalStateHandler[C0.LF]; +normalStateHandler[C0.FF] = normalStateHandler[C0.LF]; +normalStateHandler[C0.CR] = (parser, handler) => handler.carriageReturn(); +normalStateHandler[C0.BS] = (parser, handler) => handler.backspace(); +normalStateHandler[C0.HT] = (parser, handler) => handler.tab(); +normalStateHandler[C0.SO] = (parser, handler) => handler.shiftOut(); +normalStateHandler[C0.SI] = (parser, handler) => handler.shiftIn(); +normalStateHandler[C0.ESC] = (parser, handler) => parser.setState(ParserState.ESCAPED); + +// TODO: Remove terminal when parser owns params and currentParam +const escapedStateHandler: {[key: string]: (parser: Parser, terminal: any) => void} = {}; +escapedStateHandler['['] = (parser, terminal) => { + // ESC [ Control Sequence Introducer (CSI is 0x9b) + terminal.params = []; + terminal.currentParam = 0; + parser.setState(ParserState.CSI_PARAM); +}; +escapedStateHandler[']'] = (parser, terminal) => { + // ESC ] Operating System Command (OSC is 0x9d) + terminal.params = []; + terminal.currentParam = 0; + parser.setState(ParserState.OSC); +}; +escapedStateHandler['P'] = (parser, terminal) => { + // ESC P Device Control String (DCS is 0x90) + terminal.params = []; + terminal.currentParam = 0; + parser.setState(ParserState.DCS); +}; +escapedStateHandler['_'] = (parser, terminal) => { + // ESC _ Application Program Command ( APC is 0x9f). + parser.setState(ParserState.IGNORE); +}; +escapedStateHandler['^'] = (parser, terminal) => { + // ESC ^ Privacy Message ( PM is 0x9e). + parser.setState(ParserState.IGNORE); +}; +escapedStateHandler['c'] = (parser, terminal) => { + // ESC c Full Reset (RIS). + terminal.reset(); +}; +escapedStateHandler['E'] = (parser, terminal) => { + // ESC E Next Line ( NEL is 0x85). + terminal.x = 0; + terminal.index(); + parser.setState(ParserState.NORMAL); +}; +escapedStateHandler['D'] = (parser, terminal) => { + // ESC D Index ( IND is 0x84). + terminal.index(); + parser.setState(ParserState.NORMAL); +}; +escapedStateHandler['M'] = (parser, terminal) => { + // ESC M Reverse Index ( RI is 0x8d). + terminal.reverseIndex(); + parser.setState(ParserState.NORMAL); +}; +escapedStateHandler['%'] = (parser, terminal) => { + // ESC % Select default/utf-8 character set. + // @ = default, G = utf-8 + terminal.setgLevel(0); + terminal.setgCharset(0, CHARSETS.US); + parser.setState(ParserState.NORMAL); + parser.skipNextChar(); +}; + +const csiParamStateHandler: {[key: string]: (parser: Parser) => void} = {}; +csiParamStateHandler['?'] = (parser) => parser.setPrefix('?'); +csiParamStateHandler['>'] = (parser) => parser.setPrefix('>'); +csiParamStateHandler['!'] = (parser) => parser.setPrefix('!'); +csiParamStateHandler['0'] = (parser) => parser.setParam(parser.getParam() * 10); +csiParamStateHandler['1'] = (parser) => parser.setParam(parser.getParam() * 10 + 1); +csiParamStateHandler['2'] = (parser) => parser.setParam(parser.getParam() * 10 + 2); +csiParamStateHandler['3'] = (parser) => parser.setParam(parser.getParam() * 10 + 3); +csiParamStateHandler['4'] = (parser) => parser.setParam(parser.getParam() * 10 + 4); +csiParamStateHandler['5'] = (parser) => parser.setParam(parser.getParam() * 10 + 5); +csiParamStateHandler['6'] = (parser) => parser.setParam(parser.getParam() * 10 + 6); +csiParamStateHandler['7'] = (parser) => parser.setParam(parser.getParam() * 10 + 7); +csiParamStateHandler['8'] = (parser) => parser.setParam(parser.getParam() * 10 + 8); +csiParamStateHandler['9'] = (parser) => parser.setParam(parser.getParam() * 10 + 9); +csiParamStateHandler['$'] = (parser) => parser.setPostfix('$'); +csiParamStateHandler['"'] = (parser) => parser.setPostfix('"'); +csiParamStateHandler[' '] = (parser) => parser.setPostfix(' '); +csiParamStateHandler['\''] = (parser) => parser.setPostfix('\''); +csiParamStateHandler[';'] = (parser) => parser.finalizeParam(); + +const csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[], prefix: string) => void} = {}; +csiStateHandler['@'] = (handler, params, prefix) => handler.insertChars(params); +csiStateHandler['A'] = (handler, params, prefix) => handler.cursorUp(params); +csiStateHandler['B'] = (handler, params, prefix) => handler.cursorDown(params); +csiStateHandler['C'] = (handler, params, prefix) => handler.cursorForward(params); +csiStateHandler['D'] = (handler, params, prefix) => handler.cursorBackward(params); +csiStateHandler['E'] = (handler, params, prefix) => handler.cursorNextLine(params); +csiStateHandler['F'] = (handler, params, prefix) => handler.cursorPrecedingLine(params); +csiStateHandler['G'] = (handler, params, prefix) => handler.cursorCharAbsolute(params); +csiStateHandler['H'] = (handler, params, prefix) => handler.cursorPosition(params); +csiStateHandler['I'] = (handler, params, prefix) => handler.cursorForwardTab(params); +csiStateHandler['J'] = (handler, params, prefix) => handler.eraseInDisplay(params); +csiStateHandler['K'] = (handler, params, prefix) => handler.eraseInLine(params); +csiStateHandler['L'] = (handler, params, prefix) => handler.insertLines(params); +csiStateHandler['M'] = (handler, params, prefix) => handler.deleteLines(params); +csiStateHandler['P'] = (handler, params, prefix) => handler.deleteChars(params); +csiStateHandler['S'] = (handler, params, prefix) => handler.scrollUp(params); +csiStateHandler['T'] = (handler, params, prefix) => { + if (params.length < 2 && !prefix) { + handler.scrollDown(params); + } +}; +csiStateHandler['X'] = (handler, params, prefix) => handler.eraseChars(params); +csiStateHandler['Z'] = (handler, params, prefix) => handler.cursorBackwardTab(params); +csiStateHandler['`'] = (handler, params, prefix) => handler.charPosAbsolute(params); +csiStateHandler['a'] = (handler, params, prefix) => handler.HPositionRelative(params); +csiStateHandler['b'] = (handler, params, prefix) => handler.repeatPrecedingCharacter(params); +csiStateHandler['c'] = (handler, params, prefix) => handler.sendDeviceAttributes(params); +csiStateHandler['d'] = (handler, params, prefix) => handler.linePosAbsolute(params); +csiStateHandler['e'] = (handler, params, prefix) => handler.VPositionRelative(params); +csiStateHandler['f'] = (handler, params, prefix) => handler.HVPosition(params); +csiStateHandler['g'] = (handler, params, prefix) => handler.tabClear(params); +csiStateHandler['h'] = (handler, params, prefix) => handler.setMode(params); +csiStateHandler['l'] = (handler, params, prefix) => handler.resetMode(params); +csiStateHandler['m'] = (handler, params, prefix) => handler.charAttributes(params); +csiStateHandler['n'] = (handler, params, prefix) => handler.deviceStatus(params); +csiStateHandler['p'] = (handler, params, prefix) => { + switch (prefix) { + case '!': handler.softReset(params); break; + } +}; +csiStateHandler['r'] = (handler, params) => handler.setScrollRegion(params); +csiStateHandler['s'] = (handler, params) => handler.saveCursor(params); +csiStateHandler['u'] = (handler, params) => handler.restoreCursor(params); + +// TODO: Many codes/charsets appear to not be supported +// See: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html +const charsetMap = { + '0': CHARSETS.SCLD, + 'A': CHARSETS.UK, // United Kingdom + 'B': CHARSETS.US, // United States (USASCII) + '4': CHARSETS.Dutch, + 'C': CHARSETS.Finnish, + '5': CHARSETS.Finnish, + 'f': CHARSETS.French, + 'Q': CHARSETS.FrenchCanadian, + 'K': CHARSETS.German, + 'Y': CHARSETS.Italian, + 'E': CHARSETS.NorwegianDanish, + '6': CHARSETS.NorwegianDanish, + 'Z': CHARSETS.Spanish, + 'H': CHARSETS.Swedish, + '7': CHARSETS.Swedish, + '=': CHARSETS.Swiss, + '/': CHARSETS.ISOLatin // ISOLatin is actually /A +}; + +enum ParserState { + NORMAL = 0, + ESCAPED = 1, + CSI_PARAM = 2, + CSI = 3, + OSC = 4, + CHARSET = 5, + DCS = 6, + IGNORE = 7 +} + +/** + * The terminal's parser, all input into the terminal goes through the parser + * which parses and defers the actual input handling the the IInputHandler + * specified in the constructor. + */ +export class Parser { + private _state: ParserState; + private _position: number; + + // TODO: Remove terminal when handler can do everything + constructor( + private _inputHandler: IInputHandler, + private _terminal: any + ) { + this._state = ParserState.NORMAL; + } + + /** + * Parse and handle data. + * + * @param data The data to parse. + */ + public parse(data: string) { + let l = data.length, j, cs, ch, code, low; + + this._position = 0; + // apply leftover surrogate high from last write + if (this._terminal.surrogate_high) { + data = this._terminal.surrogate_high + data; + this._terminal.surrogate_high = ''; + } + + for (; this._position < l; this._position++) { + ch = data[this._position]; + + // FIXME: higher chars than 0xa0 are not allowed in escape sequences + // --> maybe move to default + code = data.charCodeAt(this._position); + if (0xD800 <= code && code <= 0xDBFF) { + // we got a surrogate high + // get surrogate low (next 2 bytes) + low = data.charCodeAt(this._position + 1); + if (isNaN(low)) { + // end of data stream, save surrogate high + this._terminal.surrogate_high = ch; + continue; + } + code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; + ch += data.charAt(this._position + 1); + } + // surrogate low - already handled above + if (0xDC00 <= code && code <= 0xDFFF) + continue; + + switch (this._state) { + case ParserState.NORMAL: + if (ch in normalStateHandler) { + normalStateHandler[ch](this, this._inputHandler); + } else { + this._inputHandler.addChar(ch, code); + } + break; + case ParserState.ESCAPED: + if (ch in escapedStateHandler) { + escapedStateHandler[ch](this, this._terminal); + // Skip switch as it was just handled + break; + } + switch (ch) { + + // ESC (,),*,+,-,. Designate G0-G2 Character Set. + case '(': // <-- this seems to get all the attention + case ')': + case '*': + case '+': + case '-': + case '.': + switch (ch) { + case '(': + this._terminal.gcharset = 0; + break; + case ')': + this._terminal.gcharset = 1; + break; + case '*': + this._terminal.gcharset = 2; + break; + case '+': + this._terminal.gcharset = 3; + break; + case '-': + this._terminal.gcharset = 1; + break; + case '.': + this._terminal.gcharset = 2; + break; + } + this._state = ParserState.CHARSET; + break; + + // Designate G3 Character Set (VT300). + // A = ISO Latin-1 Supplemental. + // Not implemented. + case '/': + this._terminal.gcharset = 3; + this._state = ParserState.CHARSET; + this._position--; + break; + + // ESC N + // Single Shift Select of G2 Character Set + // ( SS2 is 0x8e). This affects next character only. + case 'N': + break; + // ESC O + // Single Shift Select of G3 Character Set + // ( SS3 is 0x8f). This affects next character only. + case 'O': + break; + // ESC n + // Invoke the G2 Character Set as GL (LS2). + case 'n': + this._terminal.setgLevel(2); + break; + // ESC o + // Invoke the G3 Character Set as GL (LS3). + case 'o': + this._terminal.setgLevel(3); + break; + // ESC | + // Invoke the G3 Character Set as GR (LS3R). + case '|': + this._terminal.setgLevel(3); + break; + // ESC } + // Invoke the G2 Character Set as GR (LS2R). + case '}': + this._terminal.setgLevel(2); + break; + // ESC ~ + // Invoke the G1 Character Set as GR (LS1R). + case '~': + this._terminal.setgLevel(1); + break; + + // ESC 7 Save Cursor (DECSC). + case '7': + this._inputHandler.saveCursor(); + this._state = ParserState.NORMAL; + break; + + // ESC 8 Restore Cursor (DECRC). + case '8': + this._inputHandler.restoreCursor(); + this._state = ParserState.NORMAL; + break; + + // ESC # 3 DEC line height/width + case '#': + this._state = ParserState.NORMAL; + this._position++; + break; + + // ESC H Tab Set (HTS is 0x88). + case 'H': + this._terminal.tabSet(); + this._state = ParserState.NORMAL; + break; + + // ESC = Application Keypad (DECKPAM). + case '=': + this._terminal.log('Serial port requested application keypad.'); + this._terminal.applicationKeypad = true; + this._terminal.viewport.syncScrollArea(); + this._state = ParserState.NORMAL; + break; + + // ESC > Normal Keypad (DECKPNM). + case '>': + this._terminal.log('Switching back to normal keypad.'); + this._terminal.applicationKeypad = false; + this._terminal.viewport.syncScrollArea(); + this._state = ParserState.NORMAL; + break; + + default: + this._state = ParserState.NORMAL; + this._terminal.error('Unknown ESC control: %s.', ch); + break; + } + break; + + case ParserState.CHARSET: + if (ch in charsetMap) { + cs = charsetMap[ch]; + if (ch === '/') { // ISOLatin is actually /A + this.skipNextChar(); + } + } else { + cs = CHARSETS.US; // Default + } + this._terminal.setgCharset(this._terminal.gcharset, cs); + this._terminal.gcharset = null; + this._state = ParserState.NORMAL; + break; + + case ParserState.OSC: + // OSC Ps ; Pt ST + // OSC Ps ; Pt BEL + // Set Text Parameters. + if (ch === C0.ESC || ch === C0.BEL) { + if (ch === C0.ESC) this._position++; + + this._terminal.params.push(this._terminal.currentParam); + + switch (this._terminal.params[0]) { + case 0: + case 1: + case 2: + if (this._terminal.params[1]) { + this._terminal.title = this._terminal.params[1]; + this._terminal.handleTitle(this._terminal.title); + } + break; + case 3: + // set X property + break; + case 4: + case 5: + // change dynamic colors + break; + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + // change dynamic ui colors + break; + case 46: + // change log file + break; + case 50: + // dynamic font + break; + case 51: + // emacs shell + break; + case 52: + // manipulate selection data + break; + case 104: + case 105: + case 110: + case 111: + case 112: + case 113: + case 114: + case 115: + case 116: + case 117: + case 118: + // reset colors + break; + } + + this._terminal.params = []; + this._terminal.currentParam = 0; + this._state = ParserState.NORMAL; + } else { + if (!this._terminal.params.length) { + if (ch >= '0' && ch <= '9') { + this._terminal.currentParam = + this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48; + } else if (ch === ';') { + this._terminal.params.push(this._terminal.currentParam); + this._terminal.currentParam = ''; + } + } else { + this._terminal.currentParam += ch; + } + } + break; + + case ParserState.CSI_PARAM: + if (ch in csiParamStateHandler) { + csiParamStateHandler[ch](this); + break; + } + this.finalizeParam(); + // Fall through the CSI as this character should be the CSI code. + this._state = ParserState.CSI; + + case ParserState.CSI: + if (ch in csiStateHandler) { + csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix); + } else { + this._terminal.error('Unknown CSI code: %s.', ch); + } + + this._state = ParserState.NORMAL; + this._terminal.prefix = ''; + this._terminal.postfix = ''; + break; + + case ParserState.DCS: + if (ch === C0.ESC || ch === C0.BEL) { + if (ch === C0.ESC) this._position++; + + switch (this._terminal.prefix) { + // User-Defined Keys (DECUDK). + case '': + break; + + // Request Status String (DECRQSS). + // test: echo -e '\eP$q"p\e\\' + case '$q': + let pt = this._terminal.currentParam + , valid = false; + + switch (pt) { + // DECSCA + case '"q': + pt = '0"q'; + break; + + // DECSCL + case '"p': + pt = '61"p'; + break; + + // DECSTBM + case 'r': + pt = '' + + (this._terminal.scrollTop + 1) + + ';' + + (this._terminal.scrollBottom + 1) + + 'r'; + break; + + // SGR + case 'm': + pt = '0m'; + break; + + default: + this._terminal.error('Unknown DCS Pt: %s.', pt); + pt = ''; + break; + } + + this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\'); + break; + + // Set Termcap/Terminfo Data (xterm, experimental). + case '+p': + break; + + // Request Termcap/Terminfo String (xterm, experimental) + // Regular xterm does not even respond to this sequence. + // This can cause a small glitch in vim. + // test: echo -ne '\eP+q6b64\e\\' + case '+q': + // TODO: Don't declare pt twice + /*let*/ pt = this._terminal.currentParam + , valid = false; + + this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\'); + break; + + default: + this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix); + break; + } + + this._terminal.currentParam = 0; + this._terminal.prefix = ''; + this._state = ParserState.NORMAL; + } else if (!this._terminal.currentParam) { + if (!this._terminal.prefix && ch !== '$' && ch !== '+') { + this._terminal.currentParam = ch; + } else if (this._terminal.prefix.length === 2) { + this._terminal.currentParam = ch; + } else { + this._terminal.prefix += ch; + } + } else { + this._terminal.currentParam += ch; + } + break; + + case ParserState.IGNORE: + // For PM and APC. + if (ch === C0.ESC || ch === C0.BEL) { + if (ch === C0.ESC) this._position++; + this._state = ParserState.NORMAL; + } + break; + } + } + } + + /** + * Set the parser's current parsing state. + * + * @param state The new state. + */ + public setState(state: ParserState): void { + this._state = state; + } + + /** + * Sets the parsier's current prefix. CSI codes can have prefixes of '?', '>' + * or '!'. + * + * @param prefix The prefix. + */ + public setPrefix(prefix: string): void { + this._terminal.prefix = prefix; + } + + /** + * Sets the parsier's current prefix. CSI codes can have postfixes of '$', + * '"', ' ', '\''. + * + * @param postfix The postfix. + */ + public setPostfix(postfix: string): void { + this._terminal.postfix = postfix; + } + + /** + * Sets the parser's current parameter. + * + * @param param the parameter. + */ + public setParam(param: number) { + this._terminal.currentParam = param; + } + + /** + * Gets the parser's current parameter. + */ + public getParam(): number { + return this._terminal.currentParam; + } + + /** + * Finalizes the parser's current parameter, adding it to the list of + * parameters and setting the new current parameter to 0. + */ + public finalizeParam(): void { + this._terminal.params.push(this._terminal.currentParam); + this._terminal.currentParam = 0; + } + + /** + * Tell the parser to skip the next character. + */ + public skipNextChar(): void { + this._position++; + } + + /** + * Tell the parser to repeat parsing the current character (for example if it + * needs parsing using a different state. + */ + // public repeatChar(): void { + // this._position--; + // } +} diff --git a/src/xterm.js b/src/xterm.js index ed415b319d..24a840c092 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -16,9 +16,12 @@ import { Viewport } from './Viewport'; import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard'; import { CircularList } from './utils/CircularList'; import { C0 } from './EscapeSequences'; +import { InputHandler } from './InputHandler'; +import { Parser } from './Parser'; import { CharMeasure } from './utils/CharMeasure'; import * as Browser from './utils/Browser'; import * as Keyboard from './utils/Keyboard'; +import { CHARSETS } from './Charsets'; /** * Terminal Emulation References: @@ -34,11 +37,6 @@ import * as Keyboard from './utils/Keyboard'; // Let it work inside Node.js for automated testing purposes. var document = (typeof window != 'undefined') ? window.document : null; -/** - * States - */ -var normal = 0, escaped = 1, csi = 2, osc = 3, charset = 4, dcs = 5, ignore = 6; - /** * The amount of write requests to queue before sending an XOFF signal to the * pty process. This number must be small in order for ^C and similar sequences @@ -165,7 +163,6 @@ function Terminal(options) { this.cursorState = 0; this.cursorHidden = false; this.convertEol; - this.state = 0; this.queue = ''; this.scrollTop = 0; this.scrollBottom = this.rows - 1; @@ -218,6 +215,9 @@ function Terminal(options) { this.prefix = ''; this.postfix = ''; + this.inputHandler = new InputHandler(this); + this.parser = new Parser(this.inputHandler, this); + // user input states this.writeBuffer = []; this.writeInProgress = false; @@ -1477,1038 +1477,7 @@ Terminal.prototype.innerWrite = function() { this.refreshStart = this.y; this.refreshEnd = this.y; - // apply leftover surrogate high from last write - if (this.surrogate_high) { - data = this.surrogate_high + data; - this.surrogate_high = ''; - } - - for (; i < l; i++) { - ch = data[i]; - - // FIXME: higher chars than 0xa0 are not allowed in escape sequences - // --> maybe move to default - code = data.charCodeAt(i); - if (0xD800 <= code && code <= 0xDBFF) { - // we got a surrogate high - // get surrogate low (next 2 bytes) - low = data.charCodeAt(i+1); - if (isNaN(low)) { - // end of data stream, save surrogate high - this.surrogate_high = ch; - continue; - } - code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; - ch += data.charAt(i+1); - } - - // surrogate low - already handled above - if (0xDC00 <= code && code <= 0xDFFF) - continue; - switch (this.state) { - case normal: - switch (ch) { - case C0.BEL: - this.bell(); - break; - - // '\n', '\v', '\f' - case C0.LF: - case C0.VT: - case C0.FF: - if (this.convertEol) { - this.x = 0; - } - this.y++; - if (this.y > this.scrollBottom) { - this.y--; - this.scroll(); - } - break; - - // '\r' - case '\r': - this.x = 0; - break; - - // '\b' - case C0.BS: - if (this.x > 0) { - this.x--; - } - break; - - // '\t' - case C0.HT: - this.x = this.nextStop(); - break; - - // shift out - case C0.SO: - this.setgLevel(1); - break; - - // shift in - case C0.SI: - this.setgLevel(0); - break; - - // '\e' - case C0.ESC: - this.state = escaped; - break; - - default: - // ' ' - // calculate print space - // expensive call, therefore we save width in line buffer - ch_width = wcwidth(code); - - if (ch >= ' ') { - if (this.charset && this.charset[ch]) { - ch = this.charset[ch]; - } - - row = this.y + this.ybase; - - // insert combining char in last cell - // FIXME: needs handling after cursor jumps - if (!ch_width && this.x) { - // dont overflow left - if (this.lines.get(row)[this.x-1]) { - if (!this.lines.get(row)[this.x-1][2]) { - - // found empty cell after fullwidth, need to go 2 cells back - if (this.lines.get(row)[this.x-2]) - this.lines.get(row)[this.x-2][1] += ch; - - } else { - this.lines.get(row)[this.x-1][1] += ch; - } - this.updateRange(this.y); - } - break; - } - - // goto next line if ch would overflow - // TODO: needs a global min terminal width of 2 - if (this.x+ch_width-1 >= this.cols) { - // autowrap - DECAWM - if (this.wraparoundMode) { - this.x = 0; - this.y++; - if (this.y > this.scrollBottom) { - this.y--; - this.scroll(); - } - } else { - this.x = this.cols-1; - if(ch_width===2) // FIXME: check for xterm behavior - continue; - } - } - row = this.y + this.ybase; - - // insert mode: move characters to right - if (this.insertMode) { - // do this twice for a fullwidth char - for (var moves=0; moves Normal Keypad (DECKPNM). - case '>': - this.log('Switching back to normal keypad.'); - this.applicationKeypad = false; - this.viewport.syncScrollArea(); - this.state = normal; - break; - - default: - this.state = normal; - this.error('Unknown ESC control: %s.', ch); - break; - } - break; - - case charset: - switch (ch) { - case '0': // DEC Special Character and Line Drawing Set. - cs = Terminal.charsets.SCLD; - break; - case 'A': // UK - cs = Terminal.charsets.UK; - break; - case 'B': // United States (USASCII). - cs = Terminal.charsets.US; - break; - case '4': // Dutch - cs = Terminal.charsets.Dutch; - break; - case 'C': // Finnish - case '5': - cs = Terminal.charsets.Finnish; - break; - case 'R': // French - cs = Terminal.charsets.French; - break; - case 'Q': // FrenchCanadian - cs = Terminal.charsets.FrenchCanadian; - break; - case 'K': // German - cs = Terminal.charsets.German; - break; - case 'Y': // Italian - cs = Terminal.charsets.Italian; - break; - case 'E': // NorwegianDanish - case '6': - cs = Terminal.charsets.NorwegianDanish; - break; - case 'Z': // Spanish - cs = Terminal.charsets.Spanish; - break; - case 'H': // Swedish - case '7': - cs = Terminal.charsets.Swedish; - break; - case '=': // Swiss - cs = Terminal.charsets.Swiss; - break; - case '/': // ISOLatin (actually /A) - cs = Terminal.charsets.ISOLatin; - i++; - break; - default: // Default - cs = Terminal.charsets.US; - break; - } - this.setgCharset(this.gcharset, cs); - this.gcharset = null; - this.state = normal; - break; - - case osc: - // OSC Ps ; Pt ST - // OSC Ps ; Pt BEL - // Set Text Parameters. - if (ch === C0.ESC || ch === C0.BEL) { - if (ch === C0.ESC) i++; - - this.params.push(this.currentParam); - - switch (this.params[0]) { - case 0: - case 1: - case 2: - if (this.params[1]) { - this.title = this.params[1]; - this.handleTitle(this.title); - } - break; - case 3: - // set X property - break; - case 4: - case 5: - // change dynamic colors - break; - case 10: - case 11: - case 12: - case 13: - case 14: - case 15: - case 16: - case 17: - case 18: - case 19: - // change dynamic ui colors - break; - case 46: - // change log file - break; - case 50: - // dynamic font - break; - case 51: - // emacs shell - break; - case 52: - // manipulate selection data - break; - case 104: - case 105: - case 110: - case 111: - case 112: - case 113: - case 114: - case 115: - case 116: - case 117: - case 118: - // reset colors - break; - } - - this.params = []; - this.currentParam = 0; - this.state = normal; - } else { - if (!this.params.length) { - if (ch >= '0' && ch <= '9') { - this.currentParam = - this.currentParam * 10 + ch.charCodeAt(0) - 48; - } else if (ch === ';') { - this.params.push(this.currentParam); - this.currentParam = ''; - } - } else { - this.currentParam += ch; - } - } - break; - - case csi: - // '?', '>', '!' - if (ch === '?' || ch === '>' || ch === '!') { - this.prefix = ch; - break; - } - - // 0 - 9 - if (ch >= '0' && ch <= '9') { - this.currentParam = this.currentParam * 10 + ch.charCodeAt(0) - 48; - break; - } - - // '$', '"', ' ', '\'' - if (ch === '$' || ch === '"' || ch === ' ' || ch === '\'') { - this.postfix = ch; - break; - } - - this.params.push(this.currentParam); - this.currentParam = 0; - - // ';' - if (ch === ';') break; - - this.state = normal; - - switch (ch) { - // CSI Ps A - // Cursor Up Ps Times (default = 1) (CUU). - case 'A': - this.cursorUp(this.params); - break; - - // CSI Ps B - // Cursor Down Ps Times (default = 1) (CUD). - case 'B': - this.cursorDown(this.params); - break; - - // CSI Ps C - // Cursor Forward Ps Times (default = 1) (CUF). - case 'C': - this.cursorForward(this.params); - break; - - // CSI Ps D - // Cursor Backward Ps Times (default = 1) (CUB). - case 'D': - this.cursorBackward(this.params); - break; - - // CSI Ps ; Ps H - // Cursor Position [row;column] (default = [1,1]) (CUP). - case 'H': - this.cursorPos(this.params); - break; - - // CSI Ps J Erase in Display (ED). - case 'J': - this.eraseInDisplay(this.params); - break; - - // CSI Ps K Erase in Line (EL). - case 'K': - this.eraseInLine(this.params); - break; - - // CSI Pm m Character Attributes (SGR). - case 'm': - if (!this.prefix) { - this.charAttributes(this.params); - } - break; - - // CSI Ps n Device Status Report (DSR). - case 'n': - if (!this.prefix) { - this.deviceStatus(this.params); - } - break; - - /** - * Additions - */ - - // CSI Ps @ - // Insert Ps (Blank) Character(s) (default = 1) (ICH). - case '@': - this.insertChars(this.params); - break; - - // CSI Ps E - // Cursor Next Line Ps Times (default = 1) (CNL). - case 'E': - this.cursorNextLine(this.params); - break; - - // CSI Ps F - // Cursor Preceding Line Ps Times (default = 1) (CNL). - case 'F': - this.cursorPrecedingLine(this.params); - break; - - // CSI Ps G - // Cursor Character Absolute [column] (default = [row,1]) (CHA). - case 'G': - this.cursorCharAbsolute(this.params); - break; - - // CSI Ps L - // Insert Ps Line(s) (default = 1) (IL). - case 'L': - this.insertLines(this.params); - break; - - // CSI Ps M - // Delete Ps Line(s) (default = 1) (DL). - case 'M': - this.deleteLines(this.params); - break; - - // CSI Ps P - // Delete Ps Character(s) (default = 1) (DCH). - case 'P': - this.deleteChars(this.params); - break; - - // CSI Ps X - // Erase Ps Character(s) (default = 1) (ECH). - case 'X': - this.eraseChars(this.params); - break; - - // CSI Pm ` Character Position Absolute - // [column] (default = [row,1]) (HPA). - case '`': - this.charPosAbsolute(this.params); - break; - - // 141 61 a * HPR - - // Horizontal Position Relative - case 'a': - this.HPositionRelative(this.params); - break; - - // CSI P s c - // Send Device Attributes (Primary DA). - // CSI > P s c - // Send Device Attributes (Secondary DA) - case 'c': - this.sendDeviceAttributes(this.params); - break; - - // CSI Pm d - // Line Position Absolute [row] (default = [1,column]) (VPA). - case 'd': - this.linePosAbsolute(this.params); - break; - - // 145 65 e * VPR - Vertical Position Relative - case 'e': - this.VPositionRelative(this.params); - break; - - // CSI Ps ; Ps f - // Horizontal and Vertical Position [row;column] (default = - // [1,1]) (HVP). - case 'f': - this.HVPosition(this.params); - break; - - // CSI Pm h Set Mode (SM). - // CSI ? Pm h - mouse escape codes, cursor escape codes - case 'h': - this.setMode(this.params); - break; - - // CSI Pm l Reset Mode (RM). - // CSI ? Pm l - case 'l': - this.resetMode(this.params); - break; - - // CSI Ps ; Ps r - // Set Scrolling Region [top;bottom] (default = full size of win- - // dow) (DECSTBM). - // CSI ? Pm r - case 'r': - this.setScrollRegion(this.params); - break; - - // CSI s - // Save cursor (ANSI.SYS). - case 's': - this.saveCursor(this.params); - break; - - // CSI u - // Restore cursor (ANSI.SYS). - case 'u': - this.restoreCursor(this.params); - break; - - /** - * Lesser Used - */ - - // CSI Ps I - // Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). - case 'I': - this.cursorForwardTab(this.params); - break; - - // CSI Ps S Scroll up Ps lines (default = 1) (SU). - case 'S': - this.scrollUp(this.params); - break; - - // CSI Ps T Scroll down Ps lines (default = 1) (SD). - // CSI Ps ; Ps ; Ps ; Ps ; Ps T - // CSI > Ps; Ps T - case 'T': - // if (this.prefix === '>') { - // this.resetTitleModes(this.params); - // break; - // } - // if (this.params.length > 2) { - // this.initMouseTracking(this.params); - // break; - // } - if (this.params.length < 2 && !this.prefix) { - this.scrollDown(this.params); - } - break; - - // CSI Ps Z - // Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). - case 'Z': - this.cursorBackwardTab(this.params); - break; - - // CSI Ps b Repeat the preceding graphic character Ps times (REP). - case 'b': - this.repeatPrecedingCharacter(this.params); - break; - - // CSI Ps g Tab Clear (TBC). - case 'g': - this.tabClear(this.params); - break; - - // CSI Pm i Media Copy (MC). - // CSI ? Pm i - // case 'i': - // this.mediaCopy(this.params); - // break; - - // CSI Pm m Character Attributes (SGR). - // CSI > Ps; Ps m - // case 'm': // duplicate - // if (this.prefix === '>') { - // this.setResources(this.params); - // } else { - // this.charAttributes(this.params); - // } - // break; - - // CSI Ps n Device Status Report (DSR). - // CSI > Ps n - // case 'n': // duplicate - // if (this.prefix === '>') { - // this.disableModifiers(this.params); - // } else { - // this.deviceStatus(this.params); - // } - // break; - - // CSI > Ps p Set pointer mode. - // CSI ! p Soft terminal reset (DECSTR). - // CSI Ps$ p - // Request ANSI mode (DECRQM). - // CSI ? Ps$ p - // Request DEC private mode (DECRQM). - // CSI Ps ; Ps " p - case 'p': - switch (this.prefix) { - // case '>': - // this.setPointerMode(this.params); - // break; - case '!': - this.softReset(this.params); - break; - // case '?': - // if (this.postfix === '$') { - // this.requestPrivateMode(this.params); - // } - // break; - // default: - // if (this.postfix === '"') { - // this.setConformanceLevel(this.params); - // } else if (this.postfix === '$') { - // this.requestAnsiMode(this.params); - // } - // break; - } - break; - - // CSI Ps q Load LEDs (DECLL). - // CSI Ps SP q - // CSI Ps " q - // case 'q': - // if (this.postfix === ' ') { - // this.setCursorStyle(this.params); - // break; - // } - // if (this.postfix === '"') { - // this.setCharProtectionAttr(this.params); - // break; - // } - // this.loadLEDs(this.params); - // break; - - // CSI Ps ; Ps r - // Set Scrolling Region [top;bottom] (default = full size of win- - // dow) (DECSTBM). - // CSI ? Pm r - // CSI Pt; Pl; Pb; Pr; Ps$ r - // case 'r': // duplicate - // if (this.prefix === '?') { - // this.restorePrivateValues(this.params); - // } else if (this.postfix === '$') { - // this.setAttrInRectangle(this.params); - // } else { - // this.setScrollRegion(this.params); - // } - // break; - - // CSI s Save cursor (ANSI.SYS). - // CSI ? Pm s - // case 's': // duplicate - // if (this.prefix === '?') { - // this.savePrivateValues(this.params); - // } else { - // this.saveCursor(this.params); - // } - // break; - - // CSI Ps ; Ps ; Ps t - // CSI Pt; Pl; Pb; Pr; Ps$ t - // CSI > Ps; Ps t - // CSI Ps SP t - // case 't': - // if (this.postfix === '$') { - // this.reverseAttrInRectangle(this.params); - // } else if (this.postfix === ' ') { - // this.setWarningBellVolume(this.params); - // } else { - // if (this.prefix === '>') { - // this.setTitleModeFeature(this.params); - // } else { - // this.manipulateWindow(this.params); - // } - // } - // break; - - // CSI u Restore cursor (ANSI.SYS). - // CSI Ps SP u - // case 'u': // duplicate - // if (this.postfix === ' ') { - // this.setMarginBellVolume(this.params); - // } else { - // this.restoreCursor(this.params); - // } - // break; - - // CSI Pt; Pl; Pb; Pr; Pp; Pt; Pl; Pp$ v - // case 'v': - // if (this.postfix === '$') { - // this.copyRectagle(this.params); - // } - // break; - - // CSI Pt ; Pl ; Pb ; Pr ' w - // case 'w': - // if (this.postfix === '\'') { - // this.enableFilterRectangle(this.params); - // } - // break; - - // CSI Ps x Request Terminal Parameters (DECREQTPARM). - // CSI Ps x Select Attribute Change Extent (DECSACE). - // CSI Pc; Pt; Pl; Pb; Pr$ x - // case 'x': - // if (this.postfix === '$') { - // this.fillRectangle(this.params); - // } else { - // this.requestParameters(this.params); - // //this.__(this.params); - // } - // break; - - // CSI Ps ; Pu ' z - // CSI Pt; Pl; Pb; Pr$ z - // case 'z': - // if (this.postfix === '\'') { - // this.enableLocatorReporting(this.params); - // } else if (this.postfix === '$') { - // this.eraseRectangle(this.params); - // } - // break; - - // CSI Pm ' { - // CSI Pt; Pl; Pb; Pr$ { - // case '{': - // if (this.postfix === '\'') { - // this.setLocatorEvents(this.params); - // } else if (this.postfix === '$') { - // this.selectiveEraseRectangle(this.params); - // } - // break; - - // CSI Ps ' | - // case '|': - // if (this.postfix === '\'') { - // this.requestLocatorPosition(this.params); - // } - // break; - - // CSI P m SP } - // Insert P s Column(s) (default = 1) (DECIC), VT420 and up. - // case '}': - // if (this.postfix === ' ') { - // this.insertColumns(this.params); - // } - // break; - - // CSI P m SP ~ - // Delete P s Column(s) (default = 1) (DECDC), VT420 and up - // case '~': - // if (this.postfix === ' ') { - // this.deleteColumns(this.params); - // } - // break; - - default: - this.error('Unknown CSI code: %s.', ch); - break; - } - - this.prefix = ''; - this.postfix = ''; - break; - - case dcs: - if (ch === C0.ESC || ch === C0.BEL) { - if (ch === C0.ESC) i++; - - switch (this.prefix) { - // User-Defined Keys (DECUDK). - case '': - break; - - // Request Status String (DECRQSS). - // test: echo -e '\eP$q"p\e\\' - case '$q': - var pt = this.currentParam - , valid = false; - - switch (pt) { - // DECSCA - case '"q': - pt = '0"q'; - break; - - // DECSCL - case '"p': - pt = '61"p'; - break; - - // DECSTBM - case 'r': - pt = '' - + (this.scrollTop + 1) - + ';' - + (this.scrollBottom + 1) - + 'r'; - break; - - // SGR - case 'm': - pt = '0m'; - break; - - default: - this.error('Unknown DCS Pt: %s.', pt); - pt = ''; - break; - } - - this.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\'); - break; - - // Set Termcap/Terminfo Data (xterm, experimental). - case '+p': - break; - - // Request Termcap/Terminfo String (xterm, experimental) - // Regular xterm does not even respond to this sequence. - // This can cause a small glitch in vim. - // test: echo -ne '\eP+q6b64\e\\' - case '+q': - var pt = this.currentParam - , valid = false; - - this.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\'); - break; - - default: - this.error('Unknown DCS prefix: %s.', this.prefix); - break; - } - - this.currentParam = 0; - this.prefix = ''; - this.state = normal; - } else if (!this.currentParam) { - if (!this.prefix && ch !== '$' && ch !== '+') { - this.currentParam = ch; - } else if (this.prefix.length === 2) { - this.currentParam = ch; - } else { - this.prefix += ch; - } - } else { - this.currentParam += ch; - } - break; - - case ignore: - // For PM and APC. - if (ch === C0.ESC || ch === C0.BEL) { - if (ch === C0.ESC) i++; - this.state = normal; - } - break; - } - } + this.parser.parse(data); this.updateRange(this.y); this.queueRefresh(this.refreshStart, this.refreshEnd); @@ -3326,7 +2295,6 @@ Terminal.prototype.index = function() { this.y--; this.scroll(); } - this.state = normal; }; @@ -3348,7 +2316,6 @@ Terminal.prototype.reverseIndex = function() { } else { this.y--; } - this.state = normal; }; @@ -3371,1928 +2338,151 @@ Terminal.prototype.reset = function() { */ Terminal.prototype.tabSet = function() { this.tabs[this.x] = true; - this.state = normal; }; - /** - * CSI - */ - -/** - * CSI Ps A - * Cursor Up Ps Times (default = 1) (CUU). + * Helpers */ -Terminal.prototype.cursorUp = function(params) { - var param = params[0]; - if (param < 1) param = 1; - this.y -= param; - if (this.y < 0) this.y = 0; -}; - -/** - * CSI Ps B - * Cursor Down Ps Times (default = 1) (CUD). - */ -Terminal.prototype.cursorDown = function(params) { - var param = params[0]; - if (param < 1) param = 1; - this.y += param; - if (this.y >= this.rows) { - this.y = this.rows - 1; +function on(el, type, handler, capture) { + if (!Array.isArray(el)) { + el = [el]; } -}; + el.forEach(function (element) { + element.addEventListener(type, handler, capture || false); + }); +} +function off(el, type, handler, capture) { + el.removeEventListener(type, handler, capture || false); +} -/** - * CSI Ps C - * Cursor Forward Ps Times (default = 1) (CUF). - */ -Terminal.prototype.cursorForward = function(params) { - var param = params[0]; - if (param < 1) param = 1; - this.x += param; - if (this.x >= this.cols) { - this.x = this.cols - 1; +function cancel(ev, force) { + if (!this.cancelEvents && !force) { + return; } -}; - - -/** - * CSI Ps D - * Cursor Backward Ps Times (default = 1) (CUB). - */ -Terminal.prototype.cursorBackward = function(params) { - var param = params[0]; - if (param < 1) param = 1; - this.x -= param; - if (this.x < 0) this.x = 0; -}; - - -/** - * CSI Ps ; Ps H - * Cursor Position [row;column] (default = [1,1]) (CUP). - */ -Terminal.prototype.cursorPos = function(params) { - var row, col; - - row = params[0] - 1; + ev.preventDefault(); + ev.stopPropagation(); + return false; +} - if (params.length >= 2) { - col = params[1] - 1; - } else { - col = 0; +function inherits(child, parent) { + function f() { + this.constructor = child; } + f.prototype = parent.prototype; + child.prototype = new f; +} - if (row < 0) { - row = 0; - } else if (row >= this.rows) { - row = this.rows - 1; - } +// if bold is broken, we can't +// use it in the terminal. +function isBoldBroken(document) { + var body = document.getElementsByTagName('body')[0]; + var el = document.createElement('span'); + el.innerHTML = 'hello world'; + body.appendChild(el); + var w1 = el.scrollWidth; + el.style.fontWeight = 'bold'; + var w2 = el.scrollWidth; + body.removeChild(el); + return w1 !== w2; +} - if (col < 0) { - col = 0; - } else if (col >= this.cols) { - col = this.cols - 1; +function indexOf(obj, el) { + var i = obj.length; + while (i--) { + if (obj[i] === el) return i; } + return -1; +} - this.x = col; - this.y = row; -}; - +function isThirdLevelShift(term, ev) { + var thirdLevelKey = + (term.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || + (term.browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); -/** - * 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. - */ -Terminal.prototype.eraseInDisplay = function(params) { - var j; - switch (params[0]) { - case 0: - this.eraseRight(this.x, this.y); - j = this.y + 1; - for (; j < this.rows; j++) { - this.eraseLine(j); - } - break; - case 1: - this.eraseLeft(this.x, this.y); - j = this.y; - while (j--) { - this.eraseLine(j); - } - break; - case 2: - j = this.rows; - while (j--) this.eraseLine(j); - break; - case 3: - ; // no saved lines - break; + if (ev.type == 'keypress') { + return thirdLevelKey; } -}; + // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events) + return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); +} -/** - * 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. - */ -Terminal.prototype.eraseInLine = function(params) { - switch (params[0]) { - case 0: - this.eraseRight(this.x, this.y); - break; - case 1: - this.eraseLeft(this.x, this.y); - break; - case 2: - this.eraseLine(this.y); - break; - } -}; +// Expose to InputHandler (temporary) +Terminal.prototype.matchColor = matchColor; +function matchColor(r1, g1, b1) { + var hash = (r1 << 16) | (g1 << 8) | b1; -/** - * 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. - */ -Terminal.prototype.charAttributes = function(params) { - // Optimize a single SGR0. - if (params.length === 1 && params[0] === 0) { - this.curAttr = this.defAttr; - return; + if (matchColor._cache[hash] != null) { + return matchColor._cache[hash]; } - var l = params.length + var ldiff = Infinity + , li = -1 , i = 0 - , flags = this.curAttr >> 18 - , fg = (this.curAttr >> 9) & 0x1ff - , bg = this.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.defAttr >> 18; - fg = (this.defAttr >> 9) & 0x1ff; - bg = this.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.defAttr >> 9) & 0x1ff; - } else if (p === 49) { - // reset bg - bg = this.defAttr & 0x1ff; - } else if (p === 38) { - // fg color 256 - if (params[i + 1] === 2) { - i += 2; - fg = 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 = 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.defAttr >> 9) & 0x1ff; - bg = this.defAttr & 0x1ff; - } else { - this.error('Unknown SGR attribute: %d.', p); - } - } + , c + , r2 + , g2 + , b2 + , diff; - this.curAttr = (flags << 18) | (fg << 9) | bg; -}; + for (; i < Terminal.vcolors.length; i++) { + c = Terminal.vcolors[i]; + r2 = c[0]; + g2 = c[1]; + b2 = c[2]; + diff = matchColor.distance(r1, g1, b1, r2, g2, b2); -/** - * 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. - */ -Terminal.prototype.deviceStatus = function(params) { - if (!this.prefix) { - switch (params[0]) { - case 5: - // status report - this.send(C0.ESC + '[0n'); - break; - case 6: - // cursor position - this.send(C0.ESC + '[' - + (this.y + 1) - + ';' - + (this.x + 1) - + 'R'); - break; + if (diff === 0) { + li = i; + break; } - } else if (this.prefix === '?') { - // modern xterm doesnt seem to - // respond to any of these except ?6, 6, and 5 - switch (params[0]) { - case 6: - // cursor position - this.send(C0.ESC + '[?' - + (this.y + 1) - + ';' - + (this.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; + + if (diff < ldiff) { + ldiff = diff; + li = i; } } -}; + return matchColor._cache[hash] = li; +} -/** - * Additions - */ - -/** - * CSI Ps @ - * Insert Ps (Blank) Character(s) (default = 1) (ICH). - */ -Terminal.prototype.insertChars = function(params) { - var param, row, j, ch; - - param = params[0]; - if (param < 1) param = 1; - - row = this.y + this.ybase; - j = this.x; - ch = [this.eraseAttr(), ' ', 1]; // xterm +matchColor._cache = {}; - while (param-- && j < this.cols) { - this.lines.get(row).splice(j++, 0, ch); - this.lines.get(row).pop(); - } +// http://stackoverflow.com/questions/1633828 +matchColor.distance = function(r1, g1, b1, r2, g2, b2) { + return Math.pow(30 * (r1 - r2), 2) + + Math.pow(59 * (g1 - g2), 2) + + Math.pow(11 * (b1 - b2), 2); }; -/** - * CSI Ps E - * Cursor Next Line Ps Times (default = 1) (CNL). - * same as CSI Ps B ? - */ -Terminal.prototype.cursorNextLine = function(params) { - var param = params[0]; - if (param < 1) param = 1; - this.y += param; - if (this.y >= this.rows) { - this.y = this.rows - 1; +function each(obj, iter, con) { + if (obj.forEach) return obj.forEach(iter, con); + for (var i = 0; i < obj.length; i++) { + iter.call(con, obj[i], i, obj); } - this.x = 0; -}; - - -/** - * CSI Ps F - * Cursor Preceding Line Ps Times (default = 1) (CNL). - * reuse CSI Ps A ? - */ -Terminal.prototype.cursorPrecedingLine = function(params) { - var param = params[0]; - if (param < 1) param = 1; - this.y -= param; - if (this.y < 0) this.y = 0; - this.x = 0; -}; - - -/** - * CSI Ps G - * Cursor Character Absolute [column] (default = [row,1]) (CHA). - */ -Terminal.prototype.cursorCharAbsolute = function(params) { - var param = params[0]; - if (param < 1) param = 1; - this.x = param - 1; -}; - - -/** - * CSI Ps L - * Insert Ps Line(s) (default = 1) (IL). - */ -Terminal.prototype.insertLines = function(params) { - var param, row, j; - - param = params[0]; - if (param < 1) param = 1; - row = this.y + this.ybase; +} - j = this.rows - 1 - this.scrollBottom; - j = this.rows - 1 + this.ybase - j + 1; +function wasMondifierKeyOnlyEvent(ev) { + return ev.keyCode === 16 || // Shift + ev.keyCode === 17 || // Ctrl + ev.keyCode === 18; // Alt +} - while (param--) { - if (this.lines.length === this.lines.maxLength) { - // Trim the start of lines to make room for the new line - this.lines.trimStart(1); - this.ybase--; - this.ydisp--; - row--; - j--; - } - // test: echo -e '\e[44m\e[1L\e[0m' - // blankLine(true) - xterm/linux behavior - this.lines.splice(row, 0, this.blankLine(true)); - this.lines.splice(j, 1); - } - - // this.maxRange(); - this.updateRange(this.y); - this.updateRange(this.scrollBottom); -}; - - -/** - * CSI Ps M - * Delete Ps Line(s) (default = 1) (DL). - */ -Terminal.prototype.deleteLines = function(params) { - var param, row, j; - - param = params[0]; - if (param < 1) param = 1; - row = this.y + this.ybase; - - j = this.rows - 1 - this.scrollBottom; - j = this.rows - 1 + this.ybase - j; - - while (param--) { - if (this.lines.length === this.lines.maxLength) { - // Trim the start of lines to make room for the new line - this.lines.trimStart(1); - this.ybase -= 1; - this.ydisp -= 1; - } - // test: echo -e '\e[44m\e[1M\e[0m' - // blankLine(true) - xterm/linux behavior - this.lines.splice(j + 1, 0, this.blankLine(true)); - this.lines.splice(row, 1); - } - - // this.maxRange(); - this.updateRange(this.y); - this.updateRange(this.scrollBottom); -}; - - -/** - * CSI Ps P - * Delete Ps Character(s) (default = 1) (DCH). - */ -Terminal.prototype.deleteChars = function(params) { - var param, row, ch; - - param = params[0]; - if (param < 1) param = 1; - - row = this.y + this.ybase; - ch = [this.eraseAttr(), ' ', 1]; // xterm - - while (param--) { - this.lines.get(row).splice(this.x, 1); - this.lines.get(row).push(ch); - } -}; - -/** - * CSI Ps X - * Erase Ps Character(s) (default = 1) (ECH). - */ -Terminal.prototype.eraseChars = function(params) { - var param, row, j, ch; - - param = params[0]; - if (param < 1) param = 1; - - row = this.y + this.ybase; - j = this.x; - ch = [this.eraseAttr(), ' ', 1]; // xterm - - while (param-- && j < this.cols) { - this.lines.get(row)[j++] = ch; - } -}; - -/** - * CSI Pm ` Character Position Absolute - * [column] (default = [row,1]) (HPA). - */ -Terminal.prototype.charPosAbsolute = function(params) { - var param = params[0]; - if (param < 1) param = 1; - this.x = param - 1; - if (this.x >= this.cols) { - this.x = this.cols - 1; - } -}; - - -/** - * 141 61 a * HPR - - * Horizontal Position Relative - * reuse CSI Ps C ? - */ -Terminal.prototype.HPositionRelative = function(params) { - var param = params[0]; - if (param < 1) param = 1; - this.x += param; - if (this.x >= this.cols) { - this.x = this.cols - 1; - } -}; - - -/** - * CSI Ps c Send Device Attributes (Primary DA). - * Ps = 0 or omitted -> request attributes from terminal. The - * response depends on the decTerminalID resource setting. - * -> CSI ? 1 ; 2 c (``VT100 with Advanced Video Option'') - * -> CSI ? 1 ; 0 c (``VT101 with No Options'') - * -> CSI ? 6 c (``VT102'') - * -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c (``VT220'') - * The VT100-style response parameters do not mean anything by - * themselves. VT220 parameters do, telling the host what fea- - * tures the terminal supports: - * Ps = 1 -> 132-columns. - * Ps = 2 -> Printer. - * Ps = 6 -> Selective erase. - * Ps = 8 -> User-defined keys. - * Ps = 9 -> National replacement character sets. - * Ps = 1 5 -> Technical characters. - * Ps = 2 2 -> ANSI color, e.g., VT525. - * Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode). - * CSI > Ps c - * Send Device Attributes (Secondary DA). - * Ps = 0 or omitted -> request the terminal's identification - * code. The response depends on the decTerminalID resource set- - * ting. It should apply only to VT220 and up, but xterm extends - * this to VT100. - * -> CSI > Pp ; Pv ; Pc c - * where Pp denotes the terminal type - * Pp = 0 -> ``VT100''. - * Pp = 1 -> ``VT220''. - * and Pv is the firmware version (for xterm, this was originally - * the XFree86 patch number, starting with 95). In a DEC termi- - * nal, Pc indicates the ROM cartridge registration number and is - * always zero. - * More information: - * xterm/charproc.c - line 2012, for more information. - * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?) - */ -Terminal.prototype.sendDeviceAttributes = function(params) { - if (params[0] > 0) return; - - if (!this.prefix) { - if (this.is('xterm') - || this.is('rxvt-unicode') - || this.is('screen')) { - this.send(C0.ESC + '[?1;2c'); - } else if (this.is('linux')) { - this.send(C0.ESC + '[?6c'); - } - } else if (this.prefix === '>') { - // xterm and urxvt - // seem to spit this - // out around ~370 times (?). - if (this.is('xterm')) { - this.send(C0.ESC + '[>0;276;0c'); - } else if (this.is('rxvt-unicode')) { - this.send(C0.ESC + '[>85;95;0c'); - } else if (this.is('linux')) { - // not supported by linux console. - // linux console echoes parameters. - this.send(params[0] + 'c'); - } else if (this.is('screen')) { - this.send(C0.ESC + '[>83;40003;0c'); - } - } -}; - - -/** - * CSI Pm d - * Line Position Absolute [row] (default = [1,column]) (VPA). - */ -Terminal.prototype.linePosAbsolute = function(params) { - var param = params[0]; - if (param < 1) param = 1; - this.y = param - 1; - if (this.y >= this.rows) { - this.y = this.rows - 1; - } -}; - - -/** - * 145 65 e * VPR - Vertical Position Relative - * reuse CSI Ps B ? - */ -Terminal.prototype.VPositionRelative = function(params) { - var param = params[0]; - if (param < 1) param = 1; - this.y += param; - if (this.y >= this.rows) { - this.y = this.rows - 1; - } -}; - - -/** - * CSI Ps ; Ps f - * Horizontal and Vertical Position [row;column] (default = - * [1,1]) (HVP). - */ -Terminal.prototype.HVPosition = function(params) { - if (params[0] < 1) params[0] = 1; - if (params[1] < 1) params[1] = 1; - - this.y = params[0] - 1; - if (this.y >= this.rows) { - this.y = this.rows - 1; - } - - this.x = params[1] - 1; - if (this.x >= this.cols) { - this.x = this.cols - 1; - } -}; - - -/** - * CSI Pm h Set Mode (SM). - * Ps = 2 -> Keyboard Action Mode (AM). - * Ps = 4 -> Insert Mode (IRM). - * Ps = 1 2 -> Send/receive (SRM). - * Ps = 2 0 -> Automatic Newline (LNM). - * CSI ? Pm h - * DEC Private Mode Set (DECSET). - * Ps = 1 -> Application Cursor Keys (DECCKM). - * Ps = 2 -> Designate USASCII for character sets G0-G3 - * (DECANM), and set VT100 mode. - * Ps = 3 -> 132 Column Mode (DECCOLM). - * Ps = 4 -> Smooth (Slow) Scroll (DECSCLM). - * Ps = 5 -> Reverse Video (DECSCNM). - * Ps = 6 -> Origin Mode (DECOM). - * Ps = 7 -> Wraparound Mode (DECAWM). - * Ps = 8 -> Auto-repeat Keys (DECARM). - * Ps = 9 -> Send Mouse X & Y on button press. See the sec- - * tion Mouse Tracking. - * Ps = 1 0 -> Show toolbar (rxvt). - * Ps = 1 2 -> Start Blinking Cursor (att610). - * Ps = 1 8 -> Print form feed (DECPFF). - * Ps = 1 9 -> Set print extent to full screen (DECPEX). - * Ps = 2 5 -> Show Cursor (DECTCEM). - * Ps = 3 0 -> Show scrollbar (rxvt). - * Ps = 3 5 -> Enable font-shifting functions (rxvt). - * Ps = 3 8 -> Enter Tektronix Mode (DECTEK). - * Ps = 4 0 -> Allow 80 -> 132 Mode. - * Ps = 4 1 -> more(1) fix (see curses resource). - * Ps = 4 2 -> Enable Nation Replacement Character sets (DECN- - * RCM). - * Ps = 4 4 -> Turn On Margin Bell. - * Ps = 4 5 -> Reverse-wraparound Mode. - * Ps = 4 6 -> Start Logging. This is normally disabled by a - * compile-time option. - * Ps = 4 7 -> Use Alternate Screen Buffer. (This may be dis- - * abled by the titeInhibit resource). - * Ps = 6 6 -> Application keypad (DECNKM). - * Ps = 6 7 -> Backarrow key sends backspace (DECBKM). - * Ps = 1 0 0 0 -> Send Mouse X & Y on button press and - * release. See the section Mouse Tracking. - * Ps = 1 0 0 1 -> Use Hilite Mouse Tracking. - * Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking. - * Ps = 1 0 0 3 -> Use All Motion Mouse Tracking. - * Ps = 1 0 0 4 -> Send FocusIn/FocusOut events. - * Ps = 1 0 0 5 -> Enable Extended Mouse Mode. - * Ps = 1 0 1 0 -> Scroll to bottom on tty output (rxvt). - * Ps = 1 0 1 1 -> Scroll to bottom on key press (rxvt). - * Ps = 1 0 3 4 -> Interpret "meta" key, sets eighth bit. - * (enables the eightBitInput resource). - * Ps = 1 0 3 5 -> Enable special modifiers for Alt and Num- - * Lock keys. (This enables the numLock resource). - * Ps = 1 0 3 6 -> Send ESC when Meta modifies a key. (This - * enables the metaSendsEscape resource). - * Ps = 1 0 3 7 -> Send DEL from the editing-keypad Delete - * key. - * Ps = 1 0 3 9 -> Send ESC when Alt modifies a key. (This - * enables the altSendsEscape resource). - * Ps = 1 0 4 0 -> Keep selection even if not highlighted. - * (This enables the keepSelection resource). - * Ps = 1 0 4 1 -> Use the CLIPBOARD selection. (This enables - * the selectToClipboard resource). - * Ps = 1 0 4 2 -> Enable Urgency window manager hint when - * Control-G is received. (This enables the bellIsUrgent - * resource). - * Ps = 1 0 4 3 -> Enable raising of the window when Control-G - * is received. (enables the popOnBell resource). - * Ps = 1 0 4 7 -> Use Alternate Screen Buffer. (This may be - * disabled by the titeInhibit resource). - * Ps = 1 0 4 8 -> Save cursor as in DECSC. (This may be dis- - * abled by the titeInhibit resource). - * Ps = 1 0 4 9 -> Save cursor as in DECSC and use Alternate - * Screen Buffer, clearing it first. (This may be disabled by - * the titeInhibit resource). This combines the effects of the 1 - * 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based - * applications rather than the 4 7 mode. - * Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode. - * Ps = 1 0 5 1 -> Set Sun function-key mode. - * Ps = 1 0 5 2 -> Set HP function-key mode. - * Ps = 1 0 5 3 -> Set SCO function-key mode. - * Ps = 1 0 6 0 -> Set legacy keyboard emulation (X11R6). - * Ps = 1 0 6 1 -> Set VT220 keyboard emulation. - * Ps = 2 0 0 4 -> Set bracketed paste mode. - * Modes: - * http: *vt100.net/docs/vt220-rm/chapter4.html - */ -Terminal.prototype.setMode = function(params) { - if (typeof params === 'object') { - var l = params.length - , i = 0; - - for (; i < l; i++) { - this.setMode(params[i]); - } - - return; - } - - if (!this.prefix) { - switch (params) { - case 4: - this.insertMode = true; - break; - case 20: - //this.convertEol = true; - break; - } - } else if (this.prefix === '?') { - switch (params) { - case 1: - this.applicationCursor = true; - break; - case 2: - this.setgCharset(0, Terminal.charsets.US); - this.setgCharset(1, Terminal.charsets.US); - this.setgCharset(2, Terminal.charsets.US); - this.setgCharset(3, Terminal.charsets.US); - // set VT100 mode here - break; - case 3: // 132 col mode - this.savedCols = this.cols; - this.resize(132, this.rows); - break; - case 6: - this.originMode = true; - break; - case 7: - this.wraparoundMode = true; - break; - case 12: - // this.cursorBlink = true; - break; - case 66: - this.log('Serial port requested application keypad.'); - this.applicationKeypad = true; - this.viewport.syncScrollArea(); - break; - case 9: // X10 Mouse - // no release, no motion, no wheel, no modifiers. - case 1000: // vt200 mouse - // no motion. - // no modifiers, except control on the wheel. - case 1002: // button event mouse - case 1003: // any event mouse - // any event - sends motion events, - // even if there is no button held down. - this.x10Mouse = params === 9; - this.vt200Mouse = params === 1000; - this.normalMouse = params > 1000; - this.mouseEvents = true; - this.element.style.cursor = 'default'; - this.log('Binding to mouse events.'); - break; - case 1004: // send focusin/focusout events - // focusin: ^[[I - // focusout: ^[[O - this.sendFocus = true; - break; - case 1005: // utf8 ext mode mouse - this.utfMouse = true; - // for wide terminals - // simply encodes large values as utf8 characters - break; - case 1006: // sgr ext mode mouse - this.sgrMouse = true; - // for wide terminals - // does not add 32 to fields - // press: ^[[ Keyboard Action Mode (AM). - * Ps = 4 -> Replace Mode (IRM). - * Ps = 1 2 -> Send/receive (SRM). - * Ps = 2 0 -> Normal Linefeed (LNM). - * CSI ? Pm l - * DEC Private Mode Reset (DECRST). - * Ps = 1 -> Normal Cursor Keys (DECCKM). - * Ps = 2 -> Designate VT52 mode (DECANM). - * Ps = 3 -> 80 Column Mode (DECCOLM). - * Ps = 4 -> Jump (Fast) Scroll (DECSCLM). - * Ps = 5 -> Normal Video (DECSCNM). - * Ps = 6 -> Normal Cursor Mode (DECOM). - * Ps = 7 -> No Wraparound Mode (DECAWM). - * Ps = 8 -> No Auto-repeat Keys (DECARM). - * Ps = 9 -> Don't send Mouse X & Y on button press. - * Ps = 1 0 -> Hide toolbar (rxvt). - * Ps = 1 2 -> Stop Blinking Cursor (att610). - * Ps = 1 8 -> Don't print form feed (DECPFF). - * Ps = 1 9 -> Limit print to scrolling region (DECPEX). - * Ps = 2 5 -> Hide Cursor (DECTCEM). - * Ps = 3 0 -> Don't show scrollbar (rxvt). - * Ps = 3 5 -> Disable font-shifting functions (rxvt). - * Ps = 4 0 -> Disallow 80 -> 132 Mode. - * Ps = 4 1 -> No more(1) fix (see curses resource). - * Ps = 4 2 -> Disable Nation Replacement Character sets (DEC- - * NRCM). - * Ps = 4 4 -> Turn Off Margin Bell. - * Ps = 4 5 -> No Reverse-wraparound Mode. - * Ps = 4 6 -> Stop Logging. (This is normally disabled by a - * compile-time option). - * Ps = 4 7 -> Use Normal Screen Buffer. - * Ps = 6 6 -> Numeric keypad (DECNKM). - * Ps = 6 7 -> Backarrow key sends delete (DECBKM). - * Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and - * release. See the section Mouse Tracking. - * Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking. - * Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking. - * Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking. - * Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events. - * Ps = 1 0 0 5 -> Disable Extended Mouse Mode. - * Ps = 1 0 1 0 -> Don't scroll to bottom on tty output - * (rxvt). - * Ps = 1 0 1 1 -> Don't scroll to bottom on key press (rxvt). - * Ps = 1 0 3 4 -> Don't interpret "meta" key. (This disables - * the eightBitInput resource). - * Ps = 1 0 3 5 -> Disable special modifiers for Alt and Num- - * Lock keys. (This disables the numLock resource). - * Ps = 1 0 3 6 -> Don't send ESC when Meta modifies a key. - * (This disables the metaSendsEscape resource). - * Ps = 1 0 3 7 -> Send VT220 Remove from the editing-keypad - * Delete key. - * Ps = 1 0 3 9 -> Don't send ESC when Alt modifies a key. - * (This disables the altSendsEscape resource). - * Ps = 1 0 4 0 -> Do not keep selection when not highlighted. - * (This disables the keepSelection resource). - * Ps = 1 0 4 1 -> Use the PRIMARY selection. (This disables - * the selectToClipboard resource). - * Ps = 1 0 4 2 -> Disable Urgency window manager hint when - * Control-G is received. (This disables the bellIsUrgent - * resource). - * Ps = 1 0 4 3 -> Disable raising of the window when Control- - * G is received. (This disables the popOnBell resource). - * Ps = 1 0 4 7 -> Use Normal Screen Buffer, clearing screen - * first if in the Alternate Screen. (This may be disabled by - * the titeInhibit resource). - * Ps = 1 0 4 8 -> Restore cursor as in DECRC. (This may be - * disabled by the titeInhibit resource). - * Ps = 1 0 4 9 -> Use Normal Screen Buffer and restore cursor - * as in DECRC. (This may be disabled by the titeInhibit - * resource). This combines the effects of the 1 0 4 7 and 1 0 - * 4 8 modes. Use this with terminfo-based applications rather - * than the 4 7 mode. - * Ps = 1 0 5 0 -> Reset terminfo/termcap function-key mode. - * Ps = 1 0 5 1 -> Reset Sun function-key mode. - * Ps = 1 0 5 2 -> Reset HP function-key mode. - * Ps = 1 0 5 3 -> Reset SCO function-key mode. - * Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6). - * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style. - * Ps = 2 0 0 4 -> Reset bracketed paste mode. - */ -Terminal.prototype.resetMode = function(params) { - if (typeof params === 'object') { - var l = params.length - , i = 0; - - for (; i < l; i++) { - this.resetMode(params[i]); - } - - return; - } - - if (!this.prefix) { - switch (params) { - case 4: - this.insertMode = false; - break; - case 20: - //this.convertEol = false; - break; - } - } else if (this.prefix === '?') { - switch (params) { - case 1: - this.applicationCursor = false; - break; - case 3: - if (this.cols === 132 && this.savedCols) { - this.resize(this.savedCols, this.rows); - } - delete this.savedCols; - break; - case 6: - this.originMode = false; - break; - case 7: - this.wraparoundMode = false; - break; - case 12: - // this.cursorBlink = false; - break; - case 66: - this.log('Switching back to normal keypad.'); - this.applicationKeypad = false; - this.viewport.syncScrollArea(); - break; - case 9: // X10 Mouse - case 1000: // vt200 mouse - case 1002: // button event mouse - case 1003: // any event mouse - this.x10Mouse = false; - this.vt200Mouse = false; - this.normalMouse = false; - this.mouseEvents = false; - this.element.style.cursor = ''; - break; - case 1004: // send focusin/focusout events - this.sendFocus = false; - break; - case 1005: // utf8 ext mode mouse - this.utfMouse = false; - break; - case 1006: // sgr ext mode mouse - this.sgrMouse = false; - break; - case 1015: // urxvt ext mode mouse - this.urxvtMouse = false; - break; - case 25: // hide cursor - this.cursorHidden = true; - break; - case 1049: // alt screen buffer cursor - ; // FALL-THROUGH - case 47: // normal screen buffer - case 1047: // normal screen buffer - clearing it first - if (this.normal) { - this.lines = this.normal.lines; - this.ybase = this.normal.ybase; - this.ydisp = this.normal.ydisp; - this.x = this.normal.x; - this.y = this.normal.y; - this.scrollTop = this.normal.scrollTop; - this.scrollBottom = this.normal.scrollBottom; - this.tabs = this.normal.tabs; - this.normal = null; - // if (params === 1049) { - // this.x = this.savedX; - // this.y = this.savedY; - // } - this.queueRefresh(0, this.rows - 1); - this.viewport.syncScrollArea(); - this.showCursor(); - } - break; - } - } -}; - - -/** - * CSI Ps ; Ps r - * Set Scrolling Region [top;bottom] (default = full size of win- - * dow) (DECSTBM). - * CSI ? Pm r - */ -Terminal.prototype.setScrollRegion = function(params) { - if (this.prefix) return; - this.scrollTop = (params[0] || 1) - 1; - this.scrollBottom = (params[1] || this.rows) - 1; - this.x = 0; - this.y = 0; -}; - - -/** - * CSI s - * Save cursor (ANSI.SYS). - */ -Terminal.prototype.saveCursor = function(params) { - this.savedX = this.x; - this.savedY = this.y; -}; - - -/** - * CSI u - * Restore cursor (ANSI.SYS). - */ -Terminal.prototype.restoreCursor = function(params) { - this.x = this.savedX || 0; - this.y = this.savedY || 0; -}; - - -/** - * Lesser Used - */ - -/** - * CSI Ps I - * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). - */ -Terminal.prototype.cursorForwardTab = function(params) { - var param = params[0] || 1; - while (param--) { - this.x = this.nextStop(); - } -}; - - -/** - * CSI Ps S Scroll up Ps lines (default = 1) (SU). - */ -Terminal.prototype.scrollUp = function(params) { - var param = params[0] || 1; - while (param--) { - this.lines.splice(this.ybase + this.scrollTop, 1); - this.lines.splice(this.ybase + this.scrollBottom, 0, this.blankLine()); - } - // this.maxRange(); - this.updateRange(this.scrollTop); - this.updateRange(this.scrollBottom); -}; - - -/** - * CSI Ps T Scroll down Ps lines (default = 1) (SD). - */ -Terminal.prototype.scrollDown = function(params) { - var param = params[0] || 1; - while (param--) { - this.lines.splice(this.ybase + this.scrollBottom, 1); - this.lines.splice(this.ybase + this.scrollTop, 0, this.blankLine()); - } - // this.maxRange(); - this.updateRange(this.scrollTop); - this.updateRange(this.scrollBottom); -}; - - -/** - * CSI Ps ; Ps ; Ps ; Ps ; Ps T - * Initiate highlight mouse tracking. Parameters are - * [func;startx;starty;firstrow;lastrow]. See the section Mouse - * Tracking. - */ -Terminal.prototype.initMouseTracking = function(params) { - // Relevant: DECSET 1001 -}; - - -/** - * CSI > Ps; Ps T - * Reset one or more features of the title modes to the default - * value. Normally, "reset" disables the feature. It is possi- - * ble to disable the ability to reset features by compiling a - * different default for the title modes into xterm. - * Ps = 0 -> Do not set window/icon labels using hexadecimal. - * Ps = 1 -> Do not query window/icon labels using hexadeci- - * mal. - * Ps = 2 -> Do not set window/icon labels using UTF-8. - * Ps = 3 -> Do not query window/icon labels using UTF-8. - * (See discussion of "Title Modes"). - */ -Terminal.prototype.resetTitleModes = function(params) { - ; -}; - - -/** - * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). - */ -Terminal.prototype.cursorBackwardTab = function(params) { - var param = params[0] || 1; - while (param--) { - this.x = this.prevStop(); - } -}; - - -/** - * CSI Ps b Repeat the preceding graphic character Ps times (REP). - */ -Terminal.prototype.repeatPrecedingCharacter = function(params) { - var param = params[0] || 1 - , line = this.lines.get(this.ybase + this.y) - , ch = line[this.x - 1] || [this.defAttr, ' ', 1]; - - while (param--) line[this.x++] = ch; -}; - - -/** - * CSI Ps g Tab Clear (TBC). - * Ps = 0 -> Clear Current Column (default). - * Ps = 3 -> Clear All. - * Potentially: - * Ps = 2 -> Clear Stops on Line. - * http://vt100.net/annarbor/aaa-ug/section6.html - */ -Terminal.prototype.tabClear = function(params) { - var param = params[0]; - if (param <= 0) { - delete this.tabs[this.x]; - } else if (param === 3) { - this.tabs = {}; - } -}; - - -/** - * CSI Pm i Media Copy (MC). - * Ps = 0 -> Print screen (default). - * Ps = 4 -> Turn off printer controller mode. - * Ps = 5 -> Turn on printer controller mode. - * CSI ? Pm i - * Media Copy (MC, DEC-specific). - * Ps = 1 -> Print line containing cursor. - * Ps = 4 -> Turn off autoprint mode. - * Ps = 5 -> Turn on autoprint mode. - * Ps = 1 0 -> Print composed display, ignores DECPEX. - * Ps = 1 1 -> Print all pages. - */ -Terminal.prototype.mediaCopy = function(params) { - ; -}; - - -/** - * CSI > Ps; Ps m - * Set or reset resource-values used by xterm to decide whether - * to construct escape sequences holding information about the - * modifiers pressed with a given key. The first parameter iden- - * tifies the resource to set/reset. The second parameter is the - * value to assign to the resource. If the second parameter is - * omitted, the resource is reset to its initial value. - * Ps = 1 -> modifyCursorKeys. - * Ps = 2 -> modifyFunctionKeys. - * Ps = 4 -> modifyOtherKeys. - * If no parameters are given, all resources are reset to their - * initial values. - */ -Terminal.prototype.setResources = function(params) { - ; -}; - - -/** - * CSI > Ps n - * Disable modifiers which may be enabled via the CSI > Ps; Ps m - * sequence. This corresponds to a resource value of "-1", which - * cannot be set with the other sequence. The parameter identi- - * fies the resource to be disabled: - * Ps = 1 -> modifyCursorKeys. - * Ps = 2 -> modifyFunctionKeys. - * Ps = 4 -> modifyOtherKeys. - * If the parameter is omitted, modifyFunctionKeys is disabled. - * When modifyFunctionKeys is disabled, xterm uses the modifier - * keys to make an extended sequence of functions rather than - * adding a parameter to each function key to denote the modi- - * fiers. - */ -Terminal.prototype.disableModifiers = function(params) { - ; -}; - - -/** - * CSI > Ps p - * Set resource value pointerMode. This is used by xterm to - * decide whether to hide the pointer cursor as the user types. - * Valid values for the parameter: - * Ps = 0 -> never hide the pointer. - * Ps = 1 -> hide if the mouse tracking mode is not enabled. - * Ps = 2 -> always hide the pointer. If no parameter is - * given, xterm uses the default, which is 1 . - */ -Terminal.prototype.setPointerMode = function(params) { - ; -}; - - -/** - * CSI ! p Soft terminal reset (DECSTR). - * http://vt100.net/docs/vt220-rm/table4-10.html - */ -Terminal.prototype.softReset = function(params) { - this.cursorHidden = false; - this.insertMode = false; - this.originMode = false; - this.wraparoundMode = false; // autowrap - this.applicationKeypad = false; // ? - this.viewport.syncScrollArea(); - this.applicationCursor = false; - this.scrollTop = 0; - this.scrollBottom = this.rows - 1; - this.curAttr = this.defAttr; - this.x = this.y = 0; // ? - this.charset = null; - this.glevel = 0; // ?? - this.charsets = [null]; // ?? -}; - - -/** - * CSI Ps$ p - * Request ANSI mode (DECRQM). For VT300 and up, reply is - * CSI Ps; Pm$ y - * where Ps is the mode number as in RM, and Pm is the mode - * value: - * 0 - not recognized - * 1 - set - * 2 - reset - * 3 - permanently set - * 4 - permanently reset - */ -Terminal.prototype.requestAnsiMode = function(params) { - ; -}; - - -/** - * CSI ? Ps$ p - * Request DEC private mode (DECRQM). For VT300 and up, reply is - * CSI ? Ps; Pm$ p - * where Ps is the mode number as in DECSET, Pm is the mode value - * as in the ANSI DECRQM. - */ -Terminal.prototype.requestPrivateMode = function(params) { - ; -}; - - -/** - * CSI Ps ; Ps " p - * Set conformance level (DECSCL). Valid values for the first - * parameter: - * Ps = 6 1 -> VT100. - * Ps = 6 2 -> VT200. - * Ps = 6 3 -> VT300. - * Valid values for the second parameter: - * Ps = 0 -> 8-bit controls. - * Ps = 1 -> 7-bit controls (always set for VT100). - * Ps = 2 -> 8-bit controls. - */ -Terminal.prototype.setConformanceLevel = function(params) { - ; -}; - - -/** - * CSI Ps q Load LEDs (DECLL). - * Ps = 0 -> Clear all LEDS (default). - * Ps = 1 -> Light Num Lock. - * Ps = 2 -> Light Caps Lock. - * Ps = 3 -> Light Scroll Lock. - * Ps = 2 1 -> Extinguish Num Lock. - * Ps = 2 2 -> Extinguish Caps Lock. - * Ps = 2 3 -> Extinguish Scroll Lock. - */ -Terminal.prototype.loadLEDs = function(params) { - ; -}; - - -/** - * CSI Ps SP q - * Set cursor style (DECSCUSR, VT520). - * Ps = 0 -> blinking block. - * Ps = 1 -> blinking block (default). - * Ps = 2 -> steady block. - * Ps = 3 -> blinking underline. - * Ps = 4 -> steady underline. - */ -Terminal.prototype.setCursorStyle = function(params) { - ; -}; - - -/** - * CSI Ps " q - * Select character protection attribute (DECSCA). Valid values - * for the parameter: - * Ps = 0 -> DECSED and DECSEL can erase (default). - * Ps = 1 -> DECSED and DECSEL cannot erase. - * Ps = 2 -> DECSED and DECSEL can erase. - */ -Terminal.prototype.setCharProtectionAttr = function(params) { - ; -}; - - -/** - * CSI ? Pm r - * Restore DEC Private Mode Values. The value of Ps previously - * saved is restored. Ps values are the same as for DECSET. - */ -Terminal.prototype.restorePrivateValues = function(params) { - ; -}; - - -/** - * CSI Pt; Pl; Pb; Pr; Ps$ r - * Change Attributes in Rectangular Area (DECCARA), VT400 and up. - * Pt; Pl; Pb; Pr denotes the rectangle. - * Ps denotes the SGR attributes to change: 0, 1, 4, 5, 7. - * NOTE: xterm doesn't enable this code by default. - */ -Terminal.prototype.setAttrInRectangle = function(params) { - var t = params[0] - , l = params[1] - , b = params[2] - , r = params[3] - , attr = params[4]; - - var line - , i; - - for (; t < b + 1; t++) { - line = this.lines.get(this.ybase + t); - for (i = l; i < r; i++) { - line[i] = [attr, line[i][1]]; - } - } - - // this.maxRange(); - this.updateRange(params[0]); - this.updateRange(params[2]); -}; - - -/** - * CSI Pc; Pt; Pl; Pb; Pr$ x - * Fill Rectangular Area (DECFRA), VT420 and up. - * Pc is the character to use. - * Pt; Pl; Pb; Pr denotes the rectangle. - * NOTE: xterm doesn't enable this code by default. - */ -Terminal.prototype.fillRectangle = function(params) { - var ch = params[0] - , t = params[1] - , l = params[2] - , b = params[3] - , r = params[4]; - - var line - , i; - - for (; t < b + 1; t++) { - line = this.lines.get(this.ybase + t); - for (i = l; i < r; i++) { - line[i] = [line[i][0], String.fromCharCode(ch)]; - } - } - - // this.maxRange(); - this.updateRange(params[1]); - this.updateRange(params[3]); -}; - - -/** - * CSI Ps ; Pu ' z - * Enable Locator Reporting (DECELR). - * Valid values for the first parameter: - * Ps = 0 -> Locator disabled (default). - * Ps = 1 -> Locator enabled. - * Ps = 2 -> Locator enabled for one report, then disabled. - * The second parameter specifies the coordinate unit for locator - * reports. - * Valid values for the second parameter: - * Pu = 0 <- or omitted -> default to character cells. - * Pu = 1 <- device physical pixels. - * Pu = 2 <- character cells. - */ -Terminal.prototype.enableLocatorReporting = function(params) { - var val = params[0] > 0; - //this.mouseEvents = val; - //this.decLocator = val; -}; - - -/** - * CSI Pt; Pl; Pb; Pr$ z - * Erase Rectangular Area (DECERA), VT400 and up. - * Pt; Pl; Pb; Pr denotes the rectangle. - * NOTE: xterm doesn't enable this code by default. - */ -Terminal.prototype.eraseRectangle = function(params) { - var t = params[0] - , l = params[1] - , b = params[2] - , r = params[3]; - - var line - , i - , ch; - - ch = [this.eraseAttr(), ' ', 1]; // xterm? - - for (; t < b + 1; t++) { - line = this.lines.get(this.ybase + t); - for (i = l; i < r; i++) { - line[i] = ch; - } - } - - // this.maxRange(); - this.updateRange(params[0]); - this.updateRange(params[2]); -}; - - -/** - * CSI P m SP } - * Insert P s Column(s) (default = 1) (DECIC), VT420 and up. - * NOTE: xterm doesn't enable this code by default. - */ -Terminal.prototype.insertColumns = function() { - var param = params[0] - , l = this.ybase + this.rows - , ch = [this.eraseAttr(), ' ', 1] // xterm? - , i; - - while (param--) { - for (i = this.ybase; i < l; i++) { - this.lines.get(i).splice(this.x + 1, 0, ch); - this.lines.get(i).pop(); - } - } - - this.maxRange(); -}; - - -/** - * CSI P m SP ~ - * Delete P s Column(s) (default = 1) (DECDC), VT420 and up - * NOTE: xterm doesn't enable this code by default. - */ -Terminal.prototype.deleteColumns = function() { - var param = params[0] - , l = this.ybase + this.rows - , ch = [this.eraseAttr(), ' ', 1] // xterm? - , i; - - while (param--) { - for (i = this.ybase; i < l; i++) { - this.lines.get(i).splice(this.x, 1); - this.lines.get(i).push(ch); - } - } - - this.maxRange(); -}; - -function wasMondifierKeyOnlyEvent(ev) { - return ev.keyCode === 16 || // Shift - ev.keyCode === 17 || // Ctrl - ev.keyCode === 18; // Alt -} - -/** - * Character Sets - */ - -Terminal.charsets = {}; - -// DEC Special Character and Line Drawing Set. -// http://vt100.net/docs/vt102-ug/table5-13.html -// A lot of curses apps use this if they see TERM=xterm. -// testing: echo -e '\e(0a\e(B' -// The xterm output sometimes seems to conflict with the -// reference above. xterm seems in line with the reference -// when running vttest however. -// The table below now uses xterm's output from vttest. -Terminal.charsets.SCLD = { // (0 - '`': '\u25c6', // '◆' - 'a': '\u2592', // '▒' - 'b': '\u0009', // '\t' - 'c': '\u000c', // '\f' - 'd': '\u000d', // '\r' - 'e': '\u000a', // '\n' - 'f': '\u00b0', // '°' - 'g': '\u00b1', // '±' - 'h': '\u2424', // '\u2424' (NL) - 'i': '\u000b', // '\v' - 'j': '\u2518', // '┘' - 'k': '\u2510', // '┐' - 'l': '\u250c', // '┌' - 'm': '\u2514', // '└' - 'n': '\u253c', // '┼' - 'o': '\u23ba', // '⎺' - 'p': '\u23bb', // '⎻' - 'q': '\u2500', // '─' - 'r': '\u23bc', // '⎼' - 's': '\u23bd', // '⎽' - 't': '\u251c', // '├' - 'u': '\u2524', // '┤' - 'v': '\u2534', // '┴' - 'w': '\u252c', // '┬' - 'x': '\u2502', // '│' - 'y': '\u2264', // '≤' - 'z': '\u2265', // '≥' - '{': '\u03c0', // 'π' - '|': '\u2260', // '≠' - '}': '\u00a3', // '£' - '~': '\u00b7' // '·' -}; - -Terminal.charsets.UK = null; // (A -Terminal.charsets.US = null; // (B (USASCII) -Terminal.charsets.Dutch = null; // (4 -Terminal.charsets.Finnish = null; // (C or (5 -Terminal.charsets.French = null; // (R -Terminal.charsets.FrenchCanadian = null; // (Q -Terminal.charsets.German = null; // (K -Terminal.charsets.Italian = null; // (Y -Terminal.charsets.NorwegianDanish = null; // (E or (6 -Terminal.charsets.Spanish = null; // (Z -Terminal.charsets.Swedish = null; // (H or (7 -Terminal.charsets.Swiss = null; // (= -Terminal.charsets.ISOLatin = null; // /A - -/** - * Helpers - */ - -function on(el, type, handler, capture) { - if (!Array.isArray(el)) { - el = [el]; - } - el.forEach(function (element) { - element.addEventListener(type, handler, capture || false); - }); -} - -function off(el, type, handler, capture) { - el.removeEventListener(type, handler, capture || false); -} - -function cancel(ev, force) { - if (!this.cancelEvents && !force) { - return; - } - ev.preventDefault(); - ev.stopPropagation(); - return false; -} - -function inherits(child, parent) { - function f() { - this.constructor = child; - } - f.prototype = parent.prototype; - child.prototype = new f; -} - -// if bold is broken, we can't -// use it in the terminal. -function isBoldBroken(document) { - var body = document.getElementsByTagName('body')[0]; - var el = document.createElement('span'); - el.innerHTML = 'hello world'; - body.appendChild(el); - var w1 = el.scrollWidth; - el.style.fontWeight = 'bold'; - var w2 = el.scrollWidth; - body.removeChild(el); - return w1 !== w2; -} - -function indexOf(obj, el) { - var i = obj.length; - while (i--) { - if (obj[i] === el) return i; - } - return -1; -} - -function isThirdLevelShift(term, ev) { - var thirdLevelKey = - (term.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || - (term.browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); - - if (ev.type == 'keypress') { - return thirdLevelKey; - } - - // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events) - return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); -} - -function matchColor(r1, g1, b1) { - var hash = (r1 << 16) | (g1 << 8) | b1; - - if (matchColor._cache[hash] != null) { - return matchColor._cache[hash]; - } - - var ldiff = Infinity - , li = -1 - , i = 0 - , c - , r2 - , g2 - , b2 - , diff; - - for (; i < Terminal.vcolors.length; i++) { - c = Terminal.vcolors[i]; - r2 = c[0]; - g2 = c[1]; - b2 = c[2]; - - diff = matchColor.distance(r1, g1, b1, r2, g2, b2); - - if (diff === 0) { - li = i; - break; - } - - if (diff < ldiff) { - ldiff = diff; - li = i; - } - } - - return matchColor._cache[hash] = li; -} - -matchColor._cache = {}; - -// http://stackoverflow.com/questions/1633828 -matchColor.distance = function(r1, g1, b1, r2, g2, b2) { - return Math.pow(30 * (r1 - r2), 2) - + Math.pow(59 * (g1 - g2), 2) - + Math.pow(11 * (b1 - b2), 2); -}; - -function each(obj, iter, con) { - if (obj.forEach) return obj.forEach(iter, con); - for (var i = 0; i < obj.length; i++) { - iter.call(con, obj[i], i, obj); - } -} - -function keys(obj) { - if (Object.keys) return Object.keys(obj); - var key, keys = []; - for (key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - keys.push(key); +function keys(obj) { + if (Object.keys) return Object.keys(obj); + var key, keys = []; + for (key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + keys.push(key); } } return keys; } -var wcwidth = (function(opts) { - // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c - // combining characters - var COMBINING = [ - [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], - [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], - [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], - [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], - [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], - [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], - [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], - [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], - [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], - [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], - [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], - [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], - [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], - [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], - [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], - [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], - [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], - [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], - [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], - [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], - [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], - [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], - [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], - [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], - [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], - [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], - [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], - [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], - [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], - [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], - [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], - [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], - [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], - [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], - [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], - [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], - [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], - [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], - [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], - [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], - [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], - [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], - [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB], - [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], - [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], - [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], - [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], - [0xE0100, 0xE01EF] - ]; - // binary search - function bisearch(ucs) { - var min = 0; - var max = COMBINING.length - 1; - var mid; - if (ucs < COMBINING[0][0] || ucs > COMBINING[max][1]) - return false; - while (max >= min) { - mid = Math.floor((min + max) / 2); - if (ucs > COMBINING[mid][1]) - min = mid + 1; - else if (ucs < COMBINING[mid][0]) - max = mid - 1; - else - return true; - } - 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 - return 1 + - ( - ucs >= 0x1100 && - ( - ucs <= 0x115f || // Hangul Jamo init. consonants - ucs == 0x2329 || - ucs == 0x232a || - (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs != 0x303f) || // CJK..Yi - (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables - (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs - (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms - (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms - (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms - (ucs >= 0xffe0 && ucs <= 0xffe6) || - (ucs >= 0x20000 && ucs <= 0x2fffd) || - (ucs >= 0x30000 && ucs <= 0x3fffd) - ) - ); - } - return wcwidth; -})({nul: 0, control: 0}); // configurable options - /** * Expose */