From 04b1ebf1a7b8a4b99a7a2fa3919a35b14bb14199 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 8 Jan 2017 16:11:35 -0800 Subject: [PATCH 01/25] Create InputHandler which performs C0 actions Part of #459 --- src/InputHandler.ts | 50 +++++++++++++++++++++++++++++++++++++++++++++ src/Interfaces.ts | 13 ++++++++++++ src/xterm.js | 26 +++++++++-------------- 3 files changed, 73 insertions(+), 16 deletions(-) create mode 100644 src/InputHandler.ts diff --git a/src/InputHandler.ts b/src/InputHandler.ts new file mode 100644 index 00000000..38b85367 --- /dev/null +++ b/src/InputHandler.ts @@ -0,0 +1,50 @@ +import { IInputHandler, ITerminal } from './Interfaces'; + +export class InputHandler implements IInputHandler { + // TODO: We want to type _terminal when it's pulled into TS + constructor(private _terminal: any) { } + + 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(); + } + } + + 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(); + } + } + + public carriageReturn(): void { + this._terminal.x = 0; + } + + public backspace(): void { + if (this._terminal.x > 0) { + this._terminal.x--; + } + } + + public tab(): void { + this._terminal.x = this._terminal.nextStop(); + } + + public shiftOut(): void { + this._terminal.setgLevel(1); + } + + public shiftIn(): void { + this._terminal.setgLevel(0); + } +} diff --git a/src/Interfaces.ts b/src/Interfaces.ts index ff7bbef2..ae05c79f 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -32,3 +32,16 @@ export interface ITerminal { scrollDisp(disp: number, suppressScrollEvent: boolean); cancel(ev: Event, force?: boolean); } + +/** + * Handles actions generated by the parser. + */ +export interface IInputHandler { + bell(): void; + lineFeed(): void; + carriageReturn(): void; + backspace(): void; + tab(): void; + shiftOut(): void; + shiftIn(): void; +} diff --git a/src/xterm.js b/src/xterm.js index 0d9e1ca8..f33f0a9c 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -16,6 +16,7 @@ import { Viewport } from './Viewport.js'; import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard.js'; import { CircularList } from './utils/CircularList.js'; import { C0 } from './EscapeSequences'; +import { InputHandler } from './InputHandler'; import * as Browser from './utils/Browser'; import * as Keyboard from './utils/Keyboard'; @@ -203,6 +204,8 @@ function Terminal(options) { this.prefix = ''; this.postfix = ''; + this.inputHandler = new InputHandler(this); + // leftover surrogate high from previous write invocation this.surrogate_high = ''; @@ -1378,47 +1381,38 @@ Terminal.prototype.write = function(data) { case normal: switch (ch) { case C0.BEL: - this.bell(); + this.inputHandler.bell(); break; 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(); - } + this.inputHandler.lineFeed(); break; // '\r' case C0.CR: - this.x = 0; + this.inputHandler.carriageReturn(); break; // '\b' case C0.BS: - if (this.x > 0) { - this.x--; - } + this.inputHandler.backspace(); break; // '\t' case C0.HT: - this.x = this.nextStop(); + this.inputHandler.tab(); break; // shift out case C0.SO: - this.setgLevel(1); + this.inputHandler.shiftOut(); break; // shift in case C0.SI: - this.setgLevel(0); + this.inputHandler.shiftIn(); break; // '\e' From 659fb90c1c5d05ea286355e800365eb0b4176021 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 8 Jan 2017 16:21:51 -0800 Subject: [PATCH 02/25] Handle C0 codes via handler map in Parser --- src/Parser.ts | 12 ++++++++++++ src/xterm.js | 46 ++++++++++------------------------------------ 2 files changed, 22 insertions(+), 36 deletions(-) create mode 100644 src/Parser.ts diff --git a/src/Parser.ts b/src/Parser.ts new file mode 100644 index 00000000..666a9151 --- /dev/null +++ b/src/Parser.ts @@ -0,0 +1,12 @@ +import { C0 } from './EscapeSequences'; +import { IInputHandler } from './Interfaces'; + +export const normalStateHandler: {[key: string]: (handler: IInputHandler) => void} = {}; +normalStateHandler[C0.BEL] = (handler) => handler.bell(); +normalStateHandler[C0.LF] = (handler) => handler.lineFeed(); +normalStateHandler[C0.VT] = normalStateHandler[C0.LF]; +normalStateHandler[C0.FF] = normalStateHandler[C0.LF]; +normalStateHandler[C0.CR] = (handler) => handler.carriageReturn(); +normalStateHandler[C0.HT] = (handler) => handler.tab(); +normalStateHandler[C0.SO] = (handler) => handler.shiftOut(); +normalStateHandler[C0.SI] = (handler) => handler.shiftIn(); diff --git a/src/xterm.js b/src/xterm.js index f33f0a9c..7d70c268 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -17,6 +17,7 @@ import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboa import { CircularList } from './utils/CircularList.js'; import { C0 } from './EscapeSequences'; import { InputHandler } from './InputHandler'; +import * as parser from './Parser'; import * as Browser from './utils/Browser'; import * as Keyboard from './utils/Keyboard'; @@ -1377,45 +1378,18 @@ Terminal.prototype.write = function(data) { // surrogate low - already handled above if (0xDC00 <= code && code <= 0xDFFF) continue; + + if (this.state === normal) { + if (ch in parser.normalStateHandler) { + parser.normalStateHandler[ch](this.inputHandler); + // Skip switch statement (eventually everything will be handled this way + continue; + } + } + switch (this.state) { case normal: switch (ch) { - case C0.BEL: - this.inputHandler.bell(); - break; - - case C0.LF: - case C0.VT: - case C0.FF: - this.inputHandler.lineFeed(); - break; - - // '\r' - case C0.CR: - this.inputHandler.carriageReturn(); - break; - - // '\b' - case C0.BS: - this.inputHandler.backspace(); - break; - - // '\t' - case C0.HT: - this.inputHandler.tab(); - break; - - // shift out - case C0.SO: - this.inputHandler.shiftOut(); - break; - - // shift in - case C0.SI: - this.inputHandler.shiftIn(); - break; - - // '\e' case C0.ESC: this.state = escaped; break; From 665a11acc06789513b7f38fb2439576872bc5ed5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 8 Jan 2017 17:12:35 -0800 Subject: [PATCH 03/25] Add C0.BS handler back --- src/Parser.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Parser.ts b/src/Parser.ts index 666a9151..b878cedf 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -7,6 +7,7 @@ normalStateHandler[C0.LF] = (handler) => handler.lineFeed(); normalStateHandler[C0.VT] = normalStateHandler[C0.LF]; normalStateHandler[C0.FF] = normalStateHandler[C0.LF]; normalStateHandler[C0.CR] = (handler) => handler.carriageReturn(); +normalStateHandler[C0.BS] = (handler) => handler.backspace(); normalStateHandler[C0.HT] = (handler) => handler.tab(); normalStateHandler[C0.SO] = (handler) => handler.shiftOut(); normalStateHandler[C0.SI] = (handler) => handler.shiftIn(); From a31921ae735300e999e73e4986502a4ec48231f2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Jan 2017 13:45:55 -0800 Subject: [PATCH 04/25] Move parsing logic into Parser.ts --- src/Parser.ts | 1023 ++++++++++++++++++++++++++++++++++++++++++++- src/xterm.js | 1103 +------------------------------------------------ 2 files changed, 1025 insertions(+), 1101 deletions(-) diff --git a/src/Parser.ts b/src/Parser.ts index b878cedf..517103d2 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -1,7 +1,7 @@ import { C0 } from './EscapeSequences'; import { IInputHandler } from './Interfaces'; -export const normalStateHandler: {[key: string]: (handler: IInputHandler) => void} = {}; +const normalStateHandler: {[key: string]: (handler: IInputHandler) => void} = {}; normalStateHandler[C0.BEL] = (handler) => handler.bell(); normalStateHandler[C0.LF] = (handler) => handler.lineFeed(); normalStateHandler[C0.VT] = normalStateHandler[C0.LF]; @@ -11,3 +11,1024 @@ normalStateHandler[C0.BS] = (handler) => handler.backspace(); normalStateHandler[C0.HT] = (handler) => handler.tab(); normalStateHandler[C0.SO] = (handler) => handler.shiftOut(); normalStateHandler[C0.SI] = (handler) => handler.shiftIn(); + +enum ParserState { + NORMAL = 0, + ESCAPED = 1, + CSI = 2, + OSC = 3, + CHARSET = 4, + DCS = 5, + IGNORE = 6 +} + +export class Parser { + private state: ParserState; + + // TODO: Remove terminal when handler can do everything + constructor( + private _inputHandler: IInputHandler, + private _terminal: any + ) { + this.state = ParserState.NORMAL; + } + + public parse(data: string) { + let l = data.length, i = 0, j, cs, ch, code, low, ch_width, row; + + // apply leftover surrogate high from last write + if (this._terminal.surrogate_high) { + data = this._terminal.surrogate_high + data; + this._terminal.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._terminal.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; + + if (this.state === ParserState.NORMAL) { + if (ch in normalStateHandler) { + normalStateHandler[ch](this._inputHandler); + // Skip switch statement (eventually everything will be handled this way + continue; + } + } + + switch (this.state) { + case ParserState.NORMAL: + switch (ch) { + case C0.ESC: + this.state = ParserState.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 = ParserState.NORMAL; + break; + + default: + this.state = ParserState.NORMAL; + this.error('Unknown ESC control: %s.', ch); + break; + } + break; + + case ParserState.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 = 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) 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 = ParserState.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 = ParserState.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 ParserState.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 = ParserState.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 ParserState.IGNORE: + // For PM and APC. + if (ch === C0.ESC || ch === C0.BEL) { + if (ch === C0.ESC) i++; + this.state = ParserState.NORMAL; + } + break; + } + } + } +} diff --git a/src/xterm.js b/src/xterm.js index 7d70c268..42661236 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -17,7 +17,7 @@ import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboa import { CircularList } from './utils/CircularList.js'; import { C0 } from './EscapeSequences'; import { InputHandler } from './InputHandler'; -import * as parser from './Parser'; +import { Parser } from './Parser'; import * as Browser from './utils/Browser'; import * as Keyboard from './utils/Keyboard'; @@ -206,6 +206,7 @@ function Terminal(options) { this.postfix = ''; this.inputHandler = new InputHandler(this); + this.parser = new Parser(this.inputHandler, this); // leftover surrogate high from previous write invocation this.surrogate_high = ''; @@ -1346,1005 +1347,10 @@ Terminal.prototype.scrollToBottom = function() { * @param {string} text The text to write to the terminal. */ Terminal.prototype.write = function(data) { - var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row; - 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; - - if (this.state === normal) { - if (ch in parser.normalStateHandler) { - parser.normalStateHandler[ch](this.inputHandler); - // Skip switch statement (eventually everything will be handled this way - continue; - } - } - - switch (this.state) { - case normal: - switch (ch) { - 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.refresh(this.refreshStart, this.refreshEnd); @@ -4996,109 +4002,6 @@ function keys(obj) { 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 */ From 4a27b9a53f9921039a4a1e05585854adf19ae857 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Jan 2017 13:46:08 -0800 Subject: [PATCH 05/25] Move wcwidth into Parser.ts --- src/Parser.ts | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/Parser.ts b/src/Parser.ts index 517103d2..94f48149 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -1032,3 +1032,106 @@ export class Parser { } } } + +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 From 940bd2699e6a9cb9ac4d7eaecb3ccf698ca09422 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Jan 2017 13:48:36 -0800 Subject: [PATCH 06/25] Fix wcwidth TS errors --- src/Parser.ts | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Parser.ts b/src/Parser.ts index 94f48149..15719640 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -1033,10 +1033,10 @@ export class Parser { } } -var wcwidth = (function(opts) { +const wcwidth = (function(opts) { // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c // combining characters - var COMBINING = [ + const COMBINING = [ [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], @@ -1088,9 +1088,9 @@ var wcwidth = (function(opts) { ]; // binary search function bisearch(ucs) { - var min = 0; - var max = COMBINING.length - 1; - var mid; + let min = 0; + let max = COMBINING.length - 1; + let mid; if (ucs < COMBINING[0][0] || ucs > COMBINING[max][1]) return false; while (max >= min) { @@ -1114,14 +1114,18 @@ var wcwidth = (function(opts) { if (bisearch(ucs)) return 0; // if we arrive here, ucs is not a combining or C0/C1 control character - return 1 + - ( - ucs >= 0x1100 && - ( + 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 === 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 @@ -1129,9 +1133,7 @@ var wcwidth = (function(opts) { (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms (ucs >= 0xffe0 && ucs <= 0xffe6) || (ucs >= 0x20000 && ucs <= 0x2fffd) || - (ucs >= 0x30000 && ucs <= 0x3fffd) - ) - ); + (ucs >= 0x30000 && ucs <= 0x3fffd))); } return wcwidth; })({nul: 0, control: 0}); // configurable options From 991b2843d3c93c21d8e2a02a25fd0b57b1c051f3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Jan 2017 13:57:58 -0800 Subject: [PATCH 07/25] Fix references to Terminaal and fix TS lint warnings --- src/Parser.ts | 313 +++++++++++++++++++++++++------------------------- 1 file changed, 157 insertions(+), 156 deletions(-) diff --git a/src/Parser.ts b/src/Parser.ts index 15719640..b04327e1 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -51,14 +51,14 @@ export class Parser { if (0xD800 <= code && code <= 0xDBFF) { // we got a surrogate high // get surrogate low (next 2 bytes) - low = data.charCodeAt(i+1); + low = data.charCodeAt(i + 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(i+1); + ch += data.charAt(i + 1); } // surrogate low - already handled above if (0xDC00 <= code && code <= 0xDFFF) @@ -86,75 +86,75 @@ export class Parser { ch_width = wcwidth(code); if (ch >= ' ') { - if (this.charset && this.charset[ch]) { - ch = this.charset[ch]; + if (this._terminal.charset && this._terminal.charset[ch]) { + ch = this._terminal.charset[ch]; } - row = this.y + this.ybase; + row = this._terminal.y + this._terminal.ybase; // insert combining char in last cell // FIXME: needs handling after cursor jumps - if (!ch_width && this.x) { + if (!ch_width && this._terminal.x) { // dont overflow left - if (this.lines.get(row)[this.x-1]) { - if (!this.lines.get(row)[this.x-1][2]) { + 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.lines.get(row)[this.x-2]) - this.lines.get(row)[this.x-2][1] += ch; + if (this._terminal.lines.get(row)[this._terminal.x - 2]) + this._terminal.lines.get(row)[this._terminal.x - 2][1] += ch; } else { - this.lines.get(row)[this.x-1][1] += ch; + this._terminal.lines.get(row)[this._terminal.x - 1][1] += ch; } - this.updateRange(this.y); + this._terminal.updateRange(this._terminal.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) { + if (this._terminal.x + ch_width - 1 >= this._terminal.cols) { // autowrap - DECAWM - if (this.wraparoundMode) { - this.x = 0; - this.y++; - if (this.y > this.scrollBottom) { - this.y--; - this.scroll(); + 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.x = this.cols-1; - if(ch_width===2) // FIXME: check for xterm behavior + this._terminal.x = this._terminal.cols - 1; + if (ch_width === 2) // FIXME: check for xterm behavior continue; } } - row = this.y + this.ybase; + row = this._terminal.y + this._terminal.ybase; // insert mode: move characters to right - if (this.insertMode) { + if (this._terminal.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._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.error('Unknown ESC control: %s.', ch); + this._terminal.error('Unknown ESC control: %s.', ch); break; } break; @@ -395,8 +395,8 @@ export class Parser { cs = Terminal.charsets.US; break; } - this.setgCharset(this.gcharset, cs); - this.gcharset = null; + this._terminal.setgCharset(this._terminal.gcharset, cs); + this._terminal.gcharset = null; this.state = ParserState.NORMAL; break; @@ -407,15 +407,15 @@ export class Parser { if (ch === C0.ESC || ch === C0.BEL) { if (ch === C0.ESC) i++; - this.params.push(this.currentParam); + this._terminal.params.push(this._terminal.currentParam); - switch (this.params[0]) { + switch (this._terminal.params[0]) { case 0: case 1: case 2: - if (this.params[1]) { - this.title = this.params[1]; - this.handleTitle(this.title); + if (this._terminal.params[1]) { + this._terminal.title = this._terminal.params[1]; + this._terminal.handleTitle(this._terminal.title); } break; case 3: @@ -464,45 +464,45 @@ export class Parser { break; } - this.params = []; - this.currentParam = 0; + this._terminal.params = []; + this._terminal.currentParam = 0; this.state = ParserState.NORMAL; } else { - if (!this.params.length) { + if (!this._terminal.params.length) { if (ch >= '0' && ch <= '9') { - this.currentParam = - this.currentParam * 10 + ch.charCodeAt(0) - 48; + this._terminal.currentParam = + this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48; } else if (ch === ';') { - this.params.push(this.currentParam); - this.currentParam = ''; + this._terminal.params.push(this._terminal.currentParam); + this._terminal.currentParam = ''; } } else { - this.currentParam += ch; + this._terminal.currentParam += ch; } } break; - case csi: + case ParserState.CSI: // '?', '>', '!' if (ch === '?' || ch === '>' || ch === '!') { - this.prefix = ch; + this._terminal.prefix = ch; break; } // 0 - 9 if (ch >= '0' && ch <= '9') { - this.currentParam = this.currentParam * 10 + ch.charCodeAt(0) - 48; + this._terminal.currentParam = this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48; break; } // '$', '"', ' ', '\'' if (ch === '$' || ch === '"' || ch === ' ' || ch === '\'') { - this.postfix = ch; + this._terminal.postfix = ch; break; } - this.params.push(this.currentParam); - this.currentParam = 0; + this._terminal.params.push(this._terminal.currentParam); + this._terminal.currentParam = 0; // ';' if (ch === ';') break; @@ -513,54 +513,54 @@ export class Parser { // CSI Ps A // Cursor Up Ps Times (default = 1) (CUU). case 'A': - this.cursorUp(this.params); + this._terminal.cursorUp(this._terminal.params); break; // CSI Ps B // Cursor Down Ps Times (default = 1) (CUD). case 'B': - this.cursorDown(this.params); + this._terminal.cursorDown(this._terminal.params); break; // CSI Ps C // Cursor Forward Ps Times (default = 1) (CUF). case 'C': - this.cursorForward(this.params); + this._terminal.cursorForward(this._terminal.params); break; // CSI Ps D // Cursor Backward Ps Times (default = 1) (CUB). case 'D': - this.cursorBackward(this.params); + this._terminal.cursorBackward(this._terminal.params); break; // CSI Ps ; Ps H // Cursor Position [row;column] (default = [1,1]) (CUP). case 'H': - this.cursorPos(this.params); + this._terminal.cursorPos(this._terminal.params); break; // CSI Ps J Erase in Display (ED). case 'J': - this.eraseInDisplay(this.params); + this._terminal.eraseInDisplay(this._terminal.params); break; // CSI Ps K Erase in Line (EL). case 'K': - this.eraseInLine(this.params); + this._terminal.eraseInLine(this._terminal.params); break; // CSI Pm m Character Attributes (SGR). case 'm': - if (!this.prefix) { - this.charAttributes(this.params); + if (!this._terminal.prefix) { + this._terminal.charAttributes(this._terminal.params); } break; // CSI Ps n Device Status Report (DSR). case 'n': - if (!this.prefix) { - this.deviceStatus(this.params); + if (!this._terminal.prefix) { + this._terminal.deviceStatus(this._terminal.params); } break; @@ -571,61 +571,61 @@ export class Parser { // CSI Ps @ // Insert Ps (Blank) Character(s) (default = 1) (ICH). case '@': - this.insertChars(this.params); + this._terminal.insertChars(this._terminal.params); break; // CSI Ps E // Cursor Next Line Ps Times (default = 1) (CNL). case 'E': - this.cursorNextLine(this.params); + this._terminal.cursorNextLine(this._terminal.params); break; // CSI Ps F // Cursor Preceding Line Ps Times (default = 1) (CNL). case 'F': - this.cursorPrecedingLine(this.params); + this._terminal.cursorPrecedingLine(this._terminal.params); break; // CSI Ps G // Cursor Character Absolute [column] (default = [row,1]) (CHA). case 'G': - this.cursorCharAbsolute(this.params); + this._terminal.cursorCharAbsolute(this._terminal.params); break; // CSI Ps L // Insert Ps Line(s) (default = 1) (IL). case 'L': - this.insertLines(this.params); + this._terminal.insertLines(this._terminal.params); break; // CSI Ps M // Delete Ps Line(s) (default = 1) (DL). case 'M': - this.deleteLines(this.params); + this._terminal.deleteLines(this._terminal.params); break; // CSI Ps P // Delete Ps Character(s) (default = 1) (DCH). case 'P': - this.deleteChars(this.params); + this._terminal.deleteChars(this._terminal.params); break; // CSI Ps X // Erase Ps Character(s) (default = 1) (ECH). case 'X': - this.eraseChars(this.params); + this._terminal.eraseChars(this._terminal.params); break; // CSI Pm ` Character Position Absolute // [column] (default = [row,1]) (HPA). case '`': - this.charPosAbsolute(this.params); + this._terminal.charPosAbsolute(this._terminal.params); break; // 141 61 a * HPR - // Horizontal Position Relative case 'a': - this.HPositionRelative(this.params); + this._terminal.HPositionRelative(this._terminal.params); break; // CSI P s c @@ -633,37 +633,37 @@ export class Parser { // CSI > P s c // Send Device Attributes (Secondary DA) case 'c': - this.sendDeviceAttributes(this.params); + this._terminal.sendDeviceAttributes(this._terminal.params); break; // CSI Pm d // Line Position Absolute [row] (default = [1,column]) (VPA). case 'd': - this.linePosAbsolute(this.params); + this._terminal.linePosAbsolute(this._terminal.params); break; // 145 65 e * VPR - Vertical Position Relative case 'e': - this.VPositionRelative(this.params); + this._terminal.VPositionRelative(this._terminal.params); break; // CSI Ps ; Ps f // Horizontal and Vertical Position [row;column] (default = // [1,1]) (HVP). case 'f': - this.HVPosition(this.params); + this._terminal.HVPosition(this._terminal.params); break; // CSI Pm h Set Mode (SM). // CSI ? Pm h - mouse escape codes, cursor escape codes case 'h': - this.setMode(this.params); + this._terminal.setMode(this._terminal.params); break; // CSI Pm l Reset Mode (RM). // CSI ? Pm l case 'l': - this.resetMode(this.params); + this._terminal.resetMode(this._terminal.params); break; // CSI Ps ; Ps r @@ -671,19 +671,19 @@ export class Parser { // dow) (DECSTBM). // CSI ? Pm r case 'r': - this.setScrollRegion(this.params); + this._terminal.setScrollRegion(this._terminal.params); break; // CSI s // Save cursor (ANSI.SYS). case 's': - this.saveCursor(this.params); + this._terminal.saveCursor(this._terminal.params); break; // CSI u // Restore cursor (ANSI.SYS). case 'u': - this.restoreCursor(this.params); + this._terminal.restoreCursor(this._terminal.params); break; /** @@ -693,12 +693,12 @@ export class Parser { // CSI Ps I // Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). case 'I': - this.cursorForwardTab(this.params); + this._terminal.cursorForwardTab(this._terminal.params); break; // CSI Ps S Scroll up Ps lines (default = 1) (SU). case 'S': - this.scrollUp(this.params); + this._terminal.scrollUp(this._terminal.params); break; // CSI Ps T Scroll down Ps lines (default = 1) (SD). @@ -713,25 +713,25 @@ export class Parser { // this.initMouseTracking(this.params); // break; // } - if (this.params.length < 2 && !this.prefix) { - this.scrollDown(this.params); + if (this._terminal.params.length < 2 && !this._terminal.prefix) { + this._terminal.scrollDown(this._terminal.params); } break; // CSI Ps Z // Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). case 'Z': - this.cursorBackwardTab(this.params); + this._terminal.cursorBackwardTab(this._terminal.params); break; // CSI Ps b Repeat the preceding graphic character Ps times (REP). case 'b': - this.repeatPrecedingCharacter(this.params); + this._terminal.repeatPrecedingCharacter(this._terminal.params); break; // CSI Ps g Tab Clear (TBC). case 'g': - this.tabClear(this.params); + this._terminal.tabClear(this._terminal.params); break; // CSI Pm i Media Copy (MC). @@ -768,12 +768,12 @@ export class Parser { // Request DEC private mode (DECRQM). // CSI Ps ; Ps " p case 'p': - switch (this.prefix) { + switch (this._terminal.prefix) { // case '>': // this.setPointerMode(this.params); // break; case '!': - this.softReset(this.params); + this._terminal.softReset(this._terminal.params); break; // case '?': // if (this.postfix === '$') { @@ -928,19 +928,19 @@ export class Parser { // break; default: - this.error('Unknown CSI code: %s.', ch); + this._terminal.error('Unknown CSI code: %s.', ch); break; } - this.prefix = ''; - this.postfix = ''; + this._terminal.prefix = ''; + this._terminal.postfix = ''; break; case ParserState.DCS: if (ch === C0.ESC || ch === C0.BEL) { if (ch === C0.ESC) i++; - switch (this.prefix) { + switch (this._terminal.prefix) { // User-Defined Keys (DECUDK). case '': break; @@ -948,7 +948,7 @@ export class Parser { // Request Status String (DECRQSS). // test: echo -e '\eP$q"p\e\\' case '$q': - var pt = this.currentParam + let pt = this._terminal.currentParam , valid = false; switch (pt) { @@ -965,9 +965,9 @@ export class Parser { // DECSTBM case 'r': pt = '' - + (this.scrollTop + 1) + + (this._terminal.scrollTop + 1) + ';' - + (this.scrollBottom + 1) + + (this._terminal.scrollBottom + 1) + 'r'; break; @@ -977,12 +977,12 @@ export class Parser { break; default: - this.error('Unknown DCS Pt: %s.', pt); + this._terminal.error('Unknown DCS Pt: %s.', pt); pt = ''; break; } - this.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\'); + this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\'); break; // Set Termcap/Terminfo Data (xterm, experimental). @@ -994,30 +994,31 @@ export class Parser { // This can cause a small glitch in vim. // test: echo -ne '\eP+q6b64\e\\' case '+q': - var pt = this.currentParam + // TODO: Don't declare pt twice + /*let*/ pt = this._terminal.currentParam , valid = false; - this.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\'); + this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\'); break; default: - this.error('Unknown DCS prefix: %s.', this.prefix); + this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix); break; } - this.currentParam = 0; - this.prefix = ''; + this._terminal.currentParam = 0; + this._terminal.prefix = ''; this.state = ParserState.NORMAL; - } else if (!this.currentParam) { - if (!this.prefix && ch !== '$' && ch !== '+') { - this.currentParam = ch; - } else if (this.prefix.length === 2) { - this.currentParam = ch; + } 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.prefix += ch; + this._terminal.prefix += ch; } } else { - this.currentParam += ch; + this._terminal.currentParam += ch; } break; From 1848fff4c97f57310f2c90edd3e9825bbc4d0488 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Jan 2017 14:21:59 -0800 Subject: [PATCH 08/25] Pull charsets out of xterm.js --- src/Charsets.ts | 58 +++++++++++++++++++++++++++++++++++++++ src/Parser.ts | 33 +++++++++++----------- src/xterm.js | 73 ++++--------------------------------------------- 3 files changed, 81 insertions(+), 83 deletions(-) create mode 100644 src/Charsets.ts diff --git a/src/Charsets.ts b/src/Charsets.ts new file mode 100644 index 00000000..4035d453 --- /dev/null +++ b/src/Charsets.ts @@ -0,0 +1,58 @@ +// TODO: Give this a proper type +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/Parser.ts b/src/Parser.ts index b04327e1..ef10abc5 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -1,5 +1,6 @@ import { C0 } from './EscapeSequences'; import { IInputHandler } from './Interfaces'; +import { CHARSETS } from './Charsets'; const normalStateHandler: {[key: string]: (handler: IInputHandler) => void} = {}; normalStateHandler[C0.BEL] = (handler) => handler.bell(); @@ -217,7 +218,7 @@ export class Parser { case '%': // this.charset = null; this._terminal.setgLevel(0); - this._terminal.setgCharset(0, Terminal.charsets.US); + this._terminal.setgCharset(0, CHARSETS.US); this.state = ParserState.NORMAL; i++; break; @@ -346,53 +347,53 @@ export class Parser { case ParserState.CHARSET: switch (ch) { case '0': // DEC Special Character and Line Drawing Set. - cs = Terminal.charsets.SCLD; + cs = CHARSETS.SCLD; break; case 'A': // UK - cs = Terminal.charsets.UK; + cs = CHARSETS.UK; break; case 'B': // United States (USASCII). - cs = Terminal.charsets.US; + cs = CHARSETS.US; break; case '4': // Dutch - cs = Terminal.charsets.Dutch; + cs = CHARSETS.Dutch; break; case 'C': // Finnish case '5': - cs = Terminal.charsets.Finnish; + cs = CHARSETS.Finnish; break; case 'R': // French - cs = Terminal.charsets.French; + cs = CHARSETS.French; break; case 'Q': // FrenchCanadian - cs = Terminal.charsets.FrenchCanadian; + cs = CHARSETS.FrenchCanadian; break; case 'K': // German - cs = Terminal.charsets.German; + cs = CHARSETS.German; break; case 'Y': // Italian - cs = Terminal.charsets.Italian; + cs = CHARSETS.Italian; break; case 'E': // NorwegianDanish case '6': - cs = Terminal.charsets.NorwegianDanish; + cs = CHARSETS.NorwegianDanish; break; case 'Z': // Spanish - cs = Terminal.charsets.Spanish; + cs = CHARSETS.Spanish; break; case 'H': // Swedish case '7': - cs = Terminal.charsets.Swedish; + cs = CHARSETS.Swedish; break; case '=': // Swiss - cs = Terminal.charsets.Swiss; + cs = CHARSETS.Swiss; break; case '/': // ISOLatin (actually /A) - cs = Terminal.charsets.ISOLatin; + cs = CHARSETS.ISOLatin; i++; break; default: // Default - cs = Terminal.charsets.US; + cs = CHARSETS.US; break; } this._terminal.setgCharset(this._terminal.gcharset, cs); diff --git a/src/xterm.js b/src/xterm.js index 42661236..5f43c381 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -17,9 +17,10 @@ import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboa import { CircularList } from './utils/CircularList.js'; import { C0 } from './EscapeSequences'; import { InputHandler } from './InputHandler'; -import { Parser } from './Parser'; +import { Parser } from './Parser'; import * as Browser from './utils/Browser'; import * as Keyboard from './utils/Keyboard'; +import { CHARSETS } from './Charsets'; /** * Terminal Emulation References: @@ -3030,10 +3031,10 @@ Terminal.prototype.setMode = function(params) { 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); + this.setgCharset(0, CHARSETS.US); + this.setgCharset(1, CHARSETS.US); + this.setgCharset(2, CHARSETS.US); + this.setgCharset(3, CHARSETS.US); // set VT100 mode here break; case 3: // 132 col mode @@ -3806,68 +3807,6 @@ Terminal.prototype.deleteColumns = function() { this.maxRange(); }; -/** - * 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 */ From 6deaaa8ef8e87a9993a6c4737eb88b7dcc8593e4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Jan 2017 15:00:37 -0800 Subject: [PATCH 09/25] Move all parser state handling into the parser --- src/Parser.ts | 3 +++ src/xterm.js | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Parser.ts b/src/Parser.ts index ef10abc5..42d429a3 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -206,11 +206,13 @@ export class Parser { ; case 'D': this._terminal.index(); + this.state = ParserState.NORMAL; break; // ESC M Reverse Index ( RI is 0x8d). case 'M': this._terminal.reverseIndex(); + this.state = ParserState.NORMAL; break; // ESC % Select default/utf-8 character set. @@ -319,6 +321,7 @@ export class Parser { // ESC H Tab Set (HTS is 0x88). case 'H': this._terminal.tabSet(); + this.state = ParserState.NORMAL; break; // ESC = Application Keypad (DECKPAM). diff --git a/src/xterm.js b/src/xterm.js index a100f802..bf9f9853 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -148,7 +148,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; @@ -2173,7 +2172,6 @@ Terminal.prototype.index = function() { this.y--; this.scroll(); } - this.state = normal; }; @@ -2195,7 +2193,6 @@ Terminal.prototype.reverseIndex = function() { } else { this.y--; } - this.state = normal; }; @@ -2218,7 +2215,6 @@ Terminal.prototype.reset = function() { */ Terminal.prototype.tabSet = function() { this.tabs[this.x] = true; - this.state = normal; }; From 672dc1a251e01c7374c741d96f98b3def2d4a90d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Jan 2017 15:11:05 -0800 Subject: [PATCH 10/25] Start pulling CSI handlers out --- src/Parser.ts | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/Parser.ts b/src/Parser.ts index 42d429a3..9e02818d 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -12,6 +12,22 @@ normalStateHandler[C0.BS] = (handler) => handler.backspace(); normalStateHandler[C0.HT] = (handler) => handler.tab(); normalStateHandler[C0.SO] = (handler) => handler.shiftOut(); normalStateHandler[C0.SI] = (handler) => handler.shiftIn(); +// TODO: Add ESC and Default cases to normalStateHandler + +const csiStateHandler: {[key: string]: (handler: IInputHandler, parser: Parser) => void} = {}; +csiStateHandler['?'] = (_, parser) => parser.setPrefix('?'); +csiStateHandler['>'] = (_, parser) => parser.setPrefix('>'); +csiStateHandler['!'] = (_, parser) => parser.setPrefix('!'); +csiStateHandler['0'] = (_, parser) => parser.setParam(parser.getParam() * 10); +csiStateHandler['1'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 1); +csiStateHandler['2'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 2); +csiStateHandler['3'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 3); +csiStateHandler['4'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 4); +csiStateHandler['5'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 5); +csiStateHandler['6'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 6); +csiStateHandler['7'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 7); +csiStateHandler['8'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 8); +csiStateHandler['9'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 9); enum ParserState { NORMAL = 0, @@ -487,15 +503,9 @@ export class Parser { break; case ParserState.CSI: - // '?', '>', '!' - if (ch === '?' || ch === '>' || ch === '!') { - this._terminal.prefix = ch; - break; - } - - // 0 - 9 - if (ch >= '0' && ch <= '9') { - this._terminal.currentParam = this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48; + if (ch in csiStateHandler) { + csiStateHandler[ch](this._inputHandler, this); + // Skip below switch as this has handled these codes (eventually everything will be handled here break; } @@ -1036,6 +1046,18 @@ export class Parser { } } } + + public setPrefix(prefix: string) { + this._terminal.prefix = prefix; + } + + public setParam(param: number) { + this._terminal.currentParam = param; + } + + public getParam(): number { + return this._terminal.currentParam; + } } const wcwidth = (function(opts) { From 635a4d76822c2e4a9e651eed2f7e54e47879261e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Jan 2017 15:19:29 -0800 Subject: [PATCH 11/25] Handle CSI postfix --- src/Parser.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Parser.ts b/src/Parser.ts index 9e02818d..789688fc 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -28,6 +28,11 @@ csiStateHandler['6'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 6 csiStateHandler['7'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 7); csiStateHandler['8'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 8); csiStateHandler['9'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 9); +csiStateHandler['$'] = (_, parser) => parser.setPostfix('$'); +csiStateHandler['"'] = (_, parser) => parser.setPostfix('"'); +csiStateHandler[' '] = (_, parser) => parser.setPostfix(' '); +csiStateHandler['\''] = (_, parser) => parser.setPostfix('\''); +// TODO: Add remaining CSI cases enum ParserState { NORMAL = 0, @@ -509,12 +514,6 @@ export class Parser { break; } - // '$', '"', ' ', '\'' - if (ch === '$' || ch === '"' || ch === ' ' || ch === '\'') { - this._terminal.postfix = ch; - break; - } - this._terminal.params.push(this._terminal.currentParam); this._terminal.currentParam = 0; @@ -1047,7 +1046,7 @@ export class Parser { } } - public setPrefix(prefix: string) { + public setPrefix(prefix: string): void { this._terminal.prefix = prefix; } @@ -1058,6 +1057,10 @@ export class Parser { public getParam(): number { return this._terminal.currentParam; } + + public setPostfix(postfix: string): void { + this._terminal.postfix = postfix; + } } const wcwidth = (function(opts) { From 9942477bff3fe22e2858341ed3d10daffacc2878 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Jan 2017 15:38:54 -0800 Subject: [PATCH 12/25] Start converting CSI codes --- src/InputHandler.ts | 44 +++++++++++++++++++++++++ src/Interfaces.ts | 5 +++ src/Parser.ts | 80 ++++++++++++++++++++------------------------- src/xterm.js | 51 ----------------------------- 4 files changed, 84 insertions(+), 96 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 38b85367..064bb177 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -47,4 +47,48 @@ export class InputHandler implements IInputHandler { public shiftIn(): void { this._terminal.setgLevel(0); } + + 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; + } + } + + 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; + } + } + + 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; + } + } + + 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; + } + } } diff --git a/src/Interfaces.ts b/src/Interfaces.ts index ae05c79f..8eedab41 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -44,4 +44,9 @@ export interface IInputHandler { tab(): void; shiftOut(): void; shiftIn(): void; + // TODO: Make IInputHandler parameter names more friendly? (ie. cursorUp(amount: number)) + cursorUp(params: number[]): void; + cursorDown(params: number[]); + cursorForward(params: number[]); + cursorBackward(params: number[]); } diff --git a/src/Parser.ts b/src/Parser.ts index 789688fc..6bf89c4d 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -14,25 +14,31 @@ normalStateHandler[C0.SO] = (handler) => handler.shiftOut(); normalStateHandler[C0.SI] = (handler) => handler.shiftIn(); // TODO: Add ESC and Default cases to normalStateHandler -const csiStateHandler: {[key: string]: (handler: IInputHandler, parser: Parser) => void} = {}; -csiStateHandler['?'] = (_, parser) => parser.setPrefix('?'); -csiStateHandler['>'] = (_, parser) => parser.setPrefix('>'); -csiStateHandler['!'] = (_, parser) => parser.setPrefix('!'); -csiStateHandler['0'] = (_, parser) => parser.setParam(parser.getParam() * 10); -csiStateHandler['1'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 1); -csiStateHandler['2'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 2); -csiStateHandler['3'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 3); -csiStateHandler['4'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 4); -csiStateHandler['5'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 5); -csiStateHandler['6'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 6); -csiStateHandler['7'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 7); -csiStateHandler['8'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 8); -csiStateHandler['9'] = (_, parser) => parser.setParam(parser.getParam() * 10 + 9); -csiStateHandler['$'] = (_, parser) => parser.setPostfix('$'); -csiStateHandler['"'] = (_, parser) => parser.setPostfix('"'); -csiStateHandler[' '] = (_, parser) => parser.setPostfix(' '); -csiStateHandler['\''] = (_, parser) => parser.setPostfix('\''); -// TODO: Add remaining CSI cases +const csiParamStateHandler: {[key: string]: (handler: IInputHandler, 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('\''); +// TODO: Handle ';' and default (move on to ParserState.CSI (from CSI_PARAM) + +const csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[]) => void} = {}; +csiStateHandler['A'] = (handler, params) => handler.cursorUp(params); +csiStateHandler['B'] = (handler, params) => handler.cursorDown(params); +csiStateHandler['C'] = (handler, params) => handler.cursorForward(params); +csiStateHandler['D'] = (handler, params) => handler.cursorBackward(params); enum ParserState { NORMAL = 0, @@ -508,12 +514,14 @@ export class Parser { break; case ParserState.CSI: - if (ch in csiStateHandler) { - csiStateHandler[ch](this._inputHandler, this); + if (ch in csiParamStateHandler) { + csiParamStateHandler[ch](this._inputHandler, this); // Skip below switch as this has handled these codes (eventually everything will be handled here break; } + // TODO: a new state should be introduced? to differentiate checking for params and the codes + this._terminal.params.push(this._terminal.currentParam); this._terminal.currentParam = 0; @@ -522,31 +530,13 @@ export class Parser { this.state = ParserState.NORMAL; + if (ch in csiStateHandler) { + csiStateHandler[ch](this._inputHandler, this._terminal.params); + // Skip below switch as this has handled these codes (eventually everything will be handled here + break; + } + switch (ch) { - // CSI Ps A - // Cursor Up Ps Times (default = 1) (CUU). - case 'A': - this._terminal.cursorUp(this._terminal.params); - break; - - // CSI Ps B - // Cursor Down Ps Times (default = 1) (CUD). - case 'B': - this._terminal.cursorDown(this._terminal.params); - break; - - // CSI Ps C - // Cursor Forward Ps Times (default = 1) (CUF). - case 'C': - this._terminal.cursorForward(this._terminal.params); - break; - - // CSI Ps D - // Cursor Backward Ps Times (default = 1) (CUB). - case 'D': - this._terminal.cursorBackward(this._terminal.params); - break; - // CSI Ps ; Ps H // Cursor Position [row;column] (default = [1,1]) (CUP). case 'H': diff --git a/src/xterm.js b/src/xterm.js index bf9f9853..c3ef9bd6 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -2222,57 +2222,6 @@ Terminal.prototype.tabSet = function() { * CSI */ -/** - * CSI Ps A - * Cursor Up Ps Times (default = 1) (CUU). - */ -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; - } -}; - - -/** - * 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; - } -}; - - -/** - * 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 From 7572dd5fa6aa76f195d2c98f3a0c632311227f9a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Jan 2017 15:39:42 -0800 Subject: [PATCH 13/25] Remove IInputHandler from CSI_PARAM lambdas --- src/Parser.ts | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Parser.ts b/src/Parser.ts index 6bf89c4d..3cfed5bb 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -14,24 +14,24 @@ normalStateHandler[C0.SO] = (handler) => handler.shiftOut(); normalStateHandler[C0.SI] = (handler) => handler.shiftIn(); // TODO: Add ESC and Default cases to normalStateHandler -const csiParamStateHandler: {[key: string]: (handler: IInputHandler, 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('\''); +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('\''); // TODO: Handle ';' and default (move on to ParserState.CSI (from CSI_PARAM) const csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[]) => void} = {}; @@ -515,7 +515,7 @@ export class Parser { case ParserState.CSI: if (ch in csiParamStateHandler) { - csiParamStateHandler[ch](this._inputHandler, this); + csiParamStateHandler[ch](this); // Skip below switch as this has handled these codes (eventually everything will be handled here break; } From c7fa2d55b45d08442a1e33f3e5fd5af0ddc504ba Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Jan 2017 16:07:27 -0800 Subject: [PATCH 14/25] Handle remaining CSI_PARAM cases and introduce the state --- src/Parser.ts | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Parser.ts b/src/Parser.ts index 3cfed5bb..afdbc007 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -32,7 +32,7 @@ csiParamStateHandler['$'] = (parser) => parser.setPostfix('$'); csiParamStateHandler['"'] = (parser) => parser.setPostfix('"'); csiParamStateHandler[' '] = (parser) => parser.setPostfix(' '); csiParamStateHandler['\''] = (parser) => parser.setPostfix('\''); -// TODO: Handle ';' and default (move on to ParserState.CSI (from CSI_PARAM) +csiParamStateHandler[';'] = (parser) => parser.finalizeParam(); const csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[]) => void} = {}; csiStateHandler['A'] = (handler, params) => handler.cursorUp(params); @@ -43,11 +43,12 @@ csiStateHandler['D'] = (handler, params) => handler.cursorBackward(params); enum ParserState { NORMAL = 0, ESCAPED = 1, - CSI = 2, - OSC = 3, - CHARSET = 4, - DCS = 5, - IGNORE = 6 + CSI_PARAM = 2, + CSI = 3, + OSC = 4, + CHARSET = 5, + DCS = 6, + IGNORE = 7 } export class Parser { @@ -194,7 +195,7 @@ export class Parser { case '[': this._terminal.params = []; this._terminal.currentParam = 0; - this.state = ParserState.CSI; + this.state = ParserState.CSI_PARAM; break; // ESC ] Operating System Command ( OSC is 0x9d). @@ -513,21 +514,16 @@ export class Parser { } break; - case ParserState.CSI: + case ParserState.CSI_PARAM: if (ch in csiParamStateHandler) { csiParamStateHandler[ch](this); - // Skip below switch as this has handled these codes (eventually everything will be handled here break; } + this.finalizeParam(); + // Fall through the CSI as this character should be the CSI code. + this.state = ParserState.CSI; - // TODO: a new state should be introduced? to differentiate checking for params and the codes - - this._terminal.params.push(this._terminal.currentParam); - this._terminal.currentParam = 0; - - // ';' - if (ch === ';') break; - + case ParserState.CSI: this.state = ParserState.NORMAL; if (ch in csiStateHandler) { @@ -1048,6 +1044,11 @@ export class Parser { return this._terminal.currentParam; } + public finalizeParam(): void { + this._terminal.params.push(this._terminal.currentParam); + this._terminal.currentParam = 0; + } + public setPostfix(postfix: string): void { this._terminal.postfix = postfix; } From db81c28bd7a730832b62b82fec1590bc4a9b581f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Jan 2017 22:57:38 -0800 Subject: [PATCH 15/25] Support more CSI codes, remove unused CSI helper functions --- src/InputHandler.ts | 395 ++++++++++++++++++++++++++++++++++++++++++++ src/Interfaces.ts | 6 +- src/Parser.ts | 34 +--- src/xterm.js | 355 +-------------------------------------- 4 files changed, 408 insertions(+), 382 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 064bb177..a0bb61b9 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1,9 +1,14 @@ import { IInputHandler, ITerminal } from './Interfaces'; +import { C0 } from './EscapeSequences'; export class InputHandler implements IInputHandler { // TODO: We want to type _terminal when it's pulled into TS constructor(private _terminal: any) { } + /** + * BEL + * Bell (Ctrl-G). + */ public bell(): void { if (!this._terminal.visualBell) { return; @@ -15,6 +20,10 @@ export class InputHandler implements IInputHandler { } } + /** + * LF + * Line Feed or New Line (NL). (LF is Ctrl-J). + */ public lineFeed(): void { if (this._terminal.convertEol) { this._terminal.x = 0; @@ -26,28 +35,54 @@ export class InputHandler implements IInputHandler { } } + /** + * CR + * Carriage Return (Ctrl-M). + */ public carriageReturn(): void { this._terminal.x = 0; } + /** + * BS + * Backspace (Ctrl-H). + */ public backspace(): void { if (this._terminal.x > 0) { this._terminal.x--; } } + /** + * TAB + * Horizontal Tab (HT) (Ctrl-I). + */ public tab(): void { this._terminal.x = this._terminal.nextStop(); } + /** + * SO + * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the + * G1 character set. + */ public shiftOut(): void { this._terminal.setgLevel(1); } + /** + * SI + * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0 + * character set (the default). + */ public shiftIn(): void { this._terminal.setgLevel(0); } + /** + * CSI Ps A + * Cursor Up Ps Times (default = 1) (CUU). + */ public cursorUp(params: number[]): void { let param = params[0]; if (param < 1) { @@ -59,6 +94,10 @@ export class InputHandler implements IInputHandler { } } + /** + * CSI Ps B + * Cursor Down Ps Times (default = 1) (CUD). + */ public cursorDown(params: number[]) { let param = params[0]; if (param < 1) { @@ -70,6 +109,10 @@ export class InputHandler implements IInputHandler { } } + /** + * CSI Ps C + * Cursor Forward Ps Times (default = 1) (CUF). + */ public cursorForward(params: number[]) { let param = params[0]; if (param < 1) { @@ -81,6 +124,10 @@ export class InputHandler implements IInputHandler { } } + /** + * CSI Ps D + * Cursor Backward Ps Times (default = 1) (CUB). + */ public cursorBackward(params: number[]) { let param = params[0]; if (param < 1) { @@ -91,4 +138,352 @@ export class InputHandler implements IInputHandler { this._terminal.x = 0; } } + + /** + * CSI Ps ; Ps H + * Cursor Position [row;column] (default = [1,1]) (CUP). + */ + public cursorPosition(params: number[]) { + let row, col; + + row = params[0] - 1; + + if (params.length >= 2) { + col = params[1] - 1; + } else { + col = 0; + } + + if (row < 0) { + row = 0; + } else if (row >= this._terminal.rows) { + row = this._terminal.rows - 1; + } + + if (col < 0) { + col = 0; + } else if (col >= this._terminal.cols) { + col = this._terminal.cols - 1; + } + + this._terminal.x = col; + this._terminal.y = row; + } + + /** + * CSI Ps J Erase in Display (ED). + * Ps = 0 -> Erase Below (default). + * Ps = 1 -> Erase Above. + * Ps = 2 -> Erase All. + * Ps = 3 -> Erase Saved Lines (xterm). + * CSI ? Ps J + * Erase in Display (DECSED). + * Ps = 0 -> Selective Erase Below (default). + * Ps = 1 -> Selective Erase Above. + * Ps = 2 -> Selective Erase All. + */ + public eraseInDisplay(params: number[]) { + let j; + switch (params[0]) { + case 0: + this._terminal.eraseRight(this._terminal.x, this._terminal.y); + j = this._terminal.y + 1; + for (; j < this._terminal.rows; j++) { + this._terminal.eraseLine(j); + } + break; + case 1: + this._terminal.eraseLeft(this._terminal.x, this._terminal.y); + j = this._terminal.y; + while (j--) { + this._terminal.eraseLine(j); + } + break; + case 2: + j = this._terminal.rows; + while (j--) this._terminal.eraseLine(j); + break; + case 3: + ; // no saved lines + break; + } + } + + /** + * CSI Ps K Erase in Line (EL). + * Ps = 0 -> Erase to Right (default). + * Ps = 1 -> Erase to Left. + * Ps = 2 -> Erase All. + * CSI ? Ps K + * Erase in Line (DECSEL). + * Ps = 0 -> Selective Erase to Right (default). + * Ps = 1 -> Selective Erase to Left. + * Ps = 2 -> Selective Erase All. + */ + public eraseInLine(params: number[]) { + switch (params[0]) { + case 0: + this._terminal.eraseRight(this._terminal.x, this._terminal.y); + break; + case 1: + this._terminal.eraseLeft(this._terminal.x, this._terminal.y); + break; + case 2: + this._terminal.eraseLine(this._terminal.y); + break; + } + } + + /** + * CSI Pm m Character Attributes (SGR). + * Ps = 0 -> Normal (default). + * Ps = 1 -> Bold. + * Ps = 4 -> Underlined. + * Ps = 5 -> Blink (appears as Bold). + * Ps = 7 -> Inverse. + * Ps = 8 -> Invisible, i.e., hidden (VT300). + * Ps = 2 2 -> Normal (neither bold nor faint). + * Ps = 2 4 -> Not underlined. + * Ps = 2 5 -> Steady (not blinking). + * Ps = 2 7 -> Positive (not inverse). + * Ps = 2 8 -> Visible, i.e., not hidden (VT300). + * Ps = 3 0 -> Set foreground color to Black. + * Ps = 3 1 -> Set foreground color to Red. + * Ps = 3 2 -> Set foreground color to Green. + * Ps = 3 3 -> Set foreground color to Yellow. + * Ps = 3 4 -> Set foreground color to Blue. + * Ps = 3 5 -> Set foreground color to Magenta. + * Ps = 3 6 -> Set foreground color to Cyan. + * Ps = 3 7 -> Set foreground color to White. + * Ps = 3 9 -> Set foreground color to default (original). + * Ps = 4 0 -> Set background color to Black. + * Ps = 4 1 -> Set background color to Red. + * Ps = 4 2 -> Set background color to Green. + * Ps = 4 3 -> Set background color to Yellow. + * Ps = 4 4 -> Set background color to Blue. + * Ps = 4 5 -> Set background color to Magenta. + * Ps = 4 6 -> Set background color to Cyan. + * Ps = 4 7 -> Set background color to White. + * Ps = 4 9 -> Set background color to default (original). + * + * If 16-color support is compiled, the following apply. Assume + * that xterm's resources are set so that the ISO color codes are + * the first 8 of a set of 16. Then the aixterm colors are the + * bright versions of the ISO colors: + * Ps = 9 0 -> Set foreground color to Black. + * Ps = 9 1 -> Set foreground color to Red. + * Ps = 9 2 -> Set foreground color to Green. + * Ps = 9 3 -> Set foreground color to Yellow. + * Ps = 9 4 -> Set foreground color to Blue. + * Ps = 9 5 -> Set foreground color to Magenta. + * Ps = 9 6 -> Set foreground color to Cyan. + * Ps = 9 7 -> Set foreground color to White. + * Ps = 1 0 0 -> Set background color to Black. + * Ps = 1 0 1 -> Set background color to Red. + * Ps = 1 0 2 -> Set background color to Green. + * Ps = 1 0 3 -> Set background color to Yellow. + * Ps = 1 0 4 -> Set background color to Blue. + * Ps = 1 0 5 -> Set background color to Magenta. + * Ps = 1 0 6 -> Set background color to Cyan. + * Ps = 1 0 7 -> Set background color to White. + * + * If xterm is compiled with the 16-color support disabled, it + * supports the following, from rxvt: + * Ps = 1 0 0 -> Set foreground and background color to + * default. + * + * If 88- or 256-color support is compiled, the following apply. + * Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second + * Ps. + * Ps = 4 8 ; 5 ; Ps -> Set background color to the second + * Ps. + */ + public charAttributes(params: number[]) { + // Optimize a single SGR0. + if (params.length === 1 && params[0] === 0) { + this._terminal.curAttr = this._terminal.defAttr; + return; + } + + let l = params.length + , i = 0 + , flags = this._terminal.curAttr >> 18 + , fg = (this._terminal.curAttr >> 9) & 0x1ff + , bg = this._terminal.curAttr & 0x1ff + , p; + + for (; i < l; i++) { + p = params[i]; + if (p >= 30 && p <= 37) { + // fg color 8 + fg = p - 30; + } else if (p >= 40 && p <= 47) { + // bg color 8 + bg = p - 40; + } else if (p >= 90 && p <= 97) { + // fg color 16 + p += 8; + fg = p - 90; + } else if (p >= 100 && p <= 107) { + // bg color 16 + p += 8; + bg = p - 100; + } else if (p === 0) { + // default + flags = this._terminal.defAttr >> 18; + fg = (this._terminal.defAttr >> 9) & 0x1ff; + bg = this._terminal.defAttr & 0x1ff; + // flags = 0; + // fg = 0x1ff; + // bg = 0x1ff; + } else if (p === 1) { + // bold text + flags |= 1; + } else if (p === 4) { + // underlined text + flags |= 2; + } else if (p === 5) { + // blink + flags |= 4; + } else if (p === 7) { + // inverse and positive + // test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m' + flags |= 8; + } else if (p === 8) { + // invisible + flags |= 16; + } else if (p === 22) { + // not bold + flags &= ~1; + } else if (p === 24) { + // not underlined + flags &= ~2; + } else if (p === 25) { + // not blink + flags &= ~4; + } else if (p === 27) { + // not inverse + flags &= ~8; + } else if (p === 28) { + // not invisible + flags &= ~16; + } else if (p === 39) { + // reset fg + fg = (this._terminal.defAttr >> 9) & 0x1ff; + } else if (p === 49) { + // reset bg + bg = this._terminal.defAttr & 0x1ff; + } else if (p === 38) { + // fg color 256 + if (params[i + 1] === 2) { + i += 2; + fg = this._terminal.matchColor( + params[i] & 0xff, + params[i + 1] & 0xff, + params[i + 2] & 0xff); + if (fg === -1) fg = 0x1ff; + i += 2; + } else if (params[i + 1] === 5) { + i += 2; + p = params[i] & 0xff; + fg = p; + } + } else if (p === 48) { + // bg color 256 + if (params[i + 1] === 2) { + i += 2; + bg = this._terminal.matchColor( + params[i] & 0xff, + params[i + 1] & 0xff, + params[i + 2] & 0xff); + if (bg === -1) bg = 0x1ff; + i += 2; + } else if (params[i + 1] === 5) { + i += 2; + p = params[i] & 0xff; + bg = p; + } + } else if (p === 100) { + // reset fg/bg + fg = (this._terminal.defAttr >> 9) & 0x1ff; + bg = this._terminal.defAttr & 0x1ff; + } else { + this._terminal.error('Unknown SGR attribute: %d.', p); + } + } + + this._terminal.curAttr = (flags << 18) | (fg << 9) | bg; + } + + /** + * CSI Ps n Device Status Report (DSR). + * Ps = 5 -> Status Report. Result (``OK'') is + * CSI 0 n + * Ps = 6 -> Report Cursor Position (CPR) [row;column]. + * Result is + * CSI r ; c R + * CSI ? Ps n + * Device Status Report (DSR, DEC-specific). + * Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI + * ? r ; c R (assumes page is zero). + * Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready). + * or CSI ? 1 1 n (not ready). + * Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked) + * or CSI ? 2 1 n (locked). + * Ps = 2 6 -> Report Keyboard status as + * CSI ? 2 7 ; 1 ; 0 ; 0 n (North American). + * The last two parameters apply to VT400 & up, and denote key- + * board ready and LK01 respectively. + * Ps = 5 3 -> Report Locator status as + * CSI ? 5 3 n Locator available, if compiled-in, or + * CSI ? 5 0 n No Locator, if not. + */ + public deviceStatus(params: number[]) { + if (!this._terminal.prefix) { + switch (params[0]) { + case 5: + // status report + this._terminal.send(C0.ESC + '[0n'); + break; + case 6: + // cursor position + this._terminal.send(C0.ESC + '[' + + (this._terminal.y + 1) + + ';' + + (this._terminal.x + 1) + + 'R'); + break; + } + } else if (this._terminal.prefix === '?') { + // modern xterm doesnt seem to + // respond to any of these except ?6, 6, and 5 + switch (params[0]) { + case 6: + // cursor position + this._terminal.send(C0.ESC + '[?' + + (this._terminal.y + 1) + + ';' + + (this._terminal.x + 1) + + 'R'); + break; + case 15: + // no printer + // this.send(C0.ESC + '[?11n'); + break; + case 25: + // dont support user defined keys + // this.send(C0.ESC + '[?21n'); + break; + case 26: + // north american keyboard + // this.send(C0.ESC + '[?27;1;0;0n'); + break; + case 53: + // no dec locator/mouse + // this.send(C0.ESC + '[?50n'); + break; + } + } + } + } diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 8eedab41..e54a58bf 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -44,9 +44,13 @@ export interface IInputHandler { tab(): void; shiftOut(): void; shiftIn(): void; - // TODO: Make IInputHandler parameter names more friendly? (ie. cursorUp(amount: number)) cursorUp(params: number[]): void; cursorDown(params: number[]); cursorForward(params: number[]); cursorBackward(params: number[]); + cursorPosition(params: number[]); + eraseInDisplay(params: number[]); + eraseInLine(params: number[]); + charAttributes(params: number[]); + deviceStatus(params: number[]); } diff --git a/src/Parser.ts b/src/Parser.ts index afdbc007..e6f6c772 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -39,6 +39,11 @@ csiStateHandler['A'] = (handler, params) => handler.cursorUp(params); csiStateHandler['B'] = (handler, params) => handler.cursorDown(params); csiStateHandler['C'] = (handler, params) => handler.cursorForward(params); csiStateHandler['D'] = (handler, params) => handler.cursorBackward(params); +csiStateHandler['H'] = (handler, params) => handler.cursorPosition(params); +csiStateHandler['J'] = (handler, params) => handler.eraseInDisplay(params); +csiStateHandler['K'] = (handler, params) => handler.eraseInLine(params); +csiStateHandler['m'] = (handler, params) => handler.charAttributes(params); +csiStateHandler['n'] = (handler, params) => handler.deviceStatus(params); enum ParserState { NORMAL = 0, @@ -533,35 +538,6 @@ export class Parser { } switch (ch) { - // CSI Ps ; Ps H - // Cursor Position [row;column] (default = [1,1]) (CUP). - case 'H': - this._terminal.cursorPos(this._terminal.params); - break; - - // CSI Ps J Erase in Display (ED). - case 'J': - this._terminal.eraseInDisplay(this._terminal.params); - break; - - // CSI Ps K Erase in Line (EL). - case 'K': - this._terminal.eraseInLine(this._terminal.params); - break; - - // CSI Pm m Character Attributes (SGR). - case 'm': - if (!this._terminal.prefix) { - this._terminal.charAttributes(this._terminal.params); - } - break; - - // CSI Ps n Device Status Report (DSR). - case 'n': - if (!this._terminal.prefix) { - this._terminal.deviceStatus(this._terminal.params); - } - break; /** * Additions diff --git a/src/xterm.js b/src/xterm.js index c3ef9bd6..723de055 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -2223,358 +2223,6 @@ Terminal.prototype.tabSet = function() { */ -/** - * CSI Ps ; Ps H - * Cursor Position [row;column] (default = [1,1]) (CUP). - */ -Terminal.prototype.cursorPos = function(params) { - var 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.rows) { - row = this.rows - 1; - } - - if (col < 0) { - col = 0; - } else if (col >= this.cols) { - col = this.cols - 1; - } - - this.x = col; - this.y = row; -}; - - -/** - * CSI Ps J Erase in Display (ED). - * Ps = 0 -> Erase Below (default). - * Ps = 1 -> Erase Above. - * Ps = 2 -> Erase All. - * Ps = 3 -> Erase Saved Lines (xterm). - * CSI ? Ps J - * Erase in Display (DECSED). - * Ps = 0 -> Selective Erase Below (default). - * Ps = 1 -> Selective Erase Above. - * Ps = 2 -> Selective Erase All. - */ -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; - } -}; - - -/** - * 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; - } -}; - - -/** - * 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; - } - - var l = params.length - , 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); - } - } - - this.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. - */ -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; - } - } 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; - } - } -}; - - /** * Additions */ @@ -3849,6 +3497,9 @@ function isThirdLevelShift(term, ev) { return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); } +// Expose to InputHandler (temporary) +Terminal.prototype.matchColor = matchColor; + function matchColor(r1, g1, b1) { var hash = (r1 << 16) | (g1 << 8) | b1; From 411b80cdc0d46cda4472652a7fd17a8732ac6b7a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 9 Jan 2017 23:06:36 -0800 Subject: [PATCH 16/25] Add CSI @, E, F, G --- src/InputHandler.ts | 78 ++++++++++++++++++++++++++++++++++++++++++--- src/Interfaces.ts | 20 +++++++----- src/Parser.ts | 10 +++--- src/xterm.js | 60 ---------------------------------- 4 files changed, 89 insertions(+), 79 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index a0bb61b9..51c6a2d9 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -79,6 +79,26 @@ export class InputHandler implements IInputHandler { 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). @@ -139,11 +159,59 @@ export class InputHandler implements IInputHandler { } } + /** + * 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[]) { + public cursorPosition(params: number[]): void { let row, col; row = params[0] - 1; @@ -182,7 +250,7 @@ export class InputHandler implements IInputHandler { * Ps = 1 -> Selective Erase Above. * Ps = 2 -> Selective Erase All. */ - public eraseInDisplay(params: number[]) { + public eraseInDisplay(params: number[]): void { let j; switch (params[0]) { case 0: @@ -220,7 +288,7 @@ export class InputHandler implements IInputHandler { * Ps = 1 -> Selective Erase to Left. * Ps = 2 -> Selective Erase All. */ - public eraseInLine(params: number[]) { + public eraseInLine(params: number[]): void { switch (params[0]) { case 0: this._terminal.eraseRight(this._terminal.x, this._terminal.y); @@ -298,7 +366,7 @@ export class InputHandler implements IInputHandler { * Ps = 4 8 ; 5 ; Ps -> Set background color to the second * Ps. */ - public charAttributes(params: number[]) { + public charAttributes(params: number[]): void { // Optimize a single SGR0. if (params.length === 1 && params[0] === 0) { this._terminal.curAttr = this._terminal.defAttr; @@ -438,7 +506,7 @@ export class InputHandler implements IInputHandler { * CSI ? 5 3 n Locator available, if compiled-in, or * CSI ? 5 0 n No Locator, if not. */ - public deviceStatus(params: number[]) { + public deviceStatus(params: number[]): void { if (!this._terminal.prefix) { switch (params[0]) { case 5: diff --git a/src/Interfaces.ts b/src/Interfaces.ts index e54a58bf..a2cb71a6 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -44,13 +44,17 @@ export interface IInputHandler { tab(): void; shiftOut(): void; shiftIn(): void; + insertChars(params); cursorUp(params: number[]): void; - cursorDown(params: number[]); - cursorForward(params: number[]); - cursorBackward(params: number[]); - cursorPosition(params: number[]); - eraseInDisplay(params: number[]); - eraseInLine(params: number[]); - charAttributes(params: number[]); - deviceStatus(params: number[]); + cursorDown(params: number[]): void; + cursorForward(params: number[]): void; + cursorBackward(params: number[]): void; + cursorNextLine(params: number[]): void; + cursorPrecedingLine(params: number[]): void; + cursorCharAbsolute(params: number[]): void; + cursorPosition(params: number[]): void; + eraseInDisplay(params: number[]): void; + eraseInLine(params: number[]): void; + charAttributes(params: number[]): void; + deviceStatus(params: number[]): void; } diff --git a/src/Parser.ts b/src/Parser.ts index e6f6c772..9a14bdfd 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -35,10 +35,14 @@ csiParamStateHandler['\''] = (parser) => parser.setPostfix('\''); csiParamStateHandler[';'] = (parser) => parser.finalizeParam(); const csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[]) => void} = {}; +csiStateHandler['@'] = (handler, params) => handler.insertChars(params); csiStateHandler['A'] = (handler, params) => handler.cursorUp(params); csiStateHandler['B'] = (handler, params) => handler.cursorDown(params); csiStateHandler['C'] = (handler, params) => handler.cursorForward(params); csiStateHandler['D'] = (handler, params) => handler.cursorBackward(params); +csiStateHandler['E'] = (handler, params) => handler.cursorNextLine(params); +csiStateHandler['F'] = (handler, params) => handler.cursorPrecedingLine(params); +csiStateHandler['G'] = (handler, params) => handler.cursorCharAbsolute(params); csiStateHandler['H'] = (handler, params) => handler.cursorPosition(params); csiStateHandler['J'] = (handler, params) => handler.eraseInDisplay(params); csiStateHandler['K'] = (handler, params) => handler.eraseInLine(params); @@ -543,12 +547,6 @@ export class Parser { * Additions */ - // CSI Ps @ - // Insert Ps (Blank) Character(s) (default = 1) (ICH). - case '@': - this._terminal.insertChars(this._terminal.params); - break; - // CSI Ps E // Cursor Next Line Ps Times (default = 1) (CNL). case 'E': diff --git a/src/xterm.js b/src/xterm.js index 723de055..035ce101 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -2227,66 +2227,6 @@ Terminal.prototype.tabSet = function() { * 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 - - while (param-- && j < this.cols) { - this.lines.get(row).splice(j++, 0, ch); - this.lines.get(row).pop(); - } -}; - -/** - * 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; - } - 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 From f9a286a851dba75c8a77e4c12ca9bc5893a6e417 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 12 Jan 2017 20:32:15 -0800 Subject: [PATCH 17/25] Convert more CSI codes --- src/InputHandler.ts | 209 ++++++++++++++++++++++++++++++++++++++++++++ src/Interfaces.ts | 47 +++++----- src/Parser.ts | 73 ++-------------- src/xterm.js | 202 ------------------------------------------ 4 files changed, 243 insertions(+), 288 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 51c6a2d9..8ecc075b 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -302,6 +302,215 @@ export class InputHandler implements IInputHandler { } } + /** + * 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 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 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 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 m Character Attributes (SGR). * Ps = 0 -> Normal (default). diff --git a/src/Interfaces.ts b/src/Interfaces.ts index a2cb71a6..e79fe502 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -37,24 +37,31 @@ export interface ITerminal { * Handles actions generated by the parser. */ export interface IInputHandler { - bell(): void; - lineFeed(): void; - carriageReturn(): void; - backspace(): void; - tab(): void; - shiftOut(): void; - shiftIn(): void; - insertChars(params); - cursorUp(params: number[]): void; - cursorDown(params: number[]): void; - cursorForward(params: number[]): void; - cursorBackward(params: number[]): void; - cursorNextLine(params: number[]): void; - cursorPrecedingLine(params: number[]): void; - cursorCharAbsolute(params: number[]): void; - cursorPosition(params: number[]): void; - eraseInDisplay(params: number[]): void; - eraseInLine(params: number[]): void; - charAttributes(params: number[]): void; - deviceStatus(params: 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); + /** 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 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 X */ eraseChars(params: number[]): void; + /** CSI ` */ charPosAbsolute(params: number[]): void; + /** CSI a */ HPositionRelative(params: number[]): void; + /** CSI c */ sendDeviceAttributes(params: number[]): void; + /** CSI m */ charAttributes(params: number[]): void; + /** CSI n */ deviceStatus(params: number[]): void; } diff --git a/src/Parser.ts b/src/Parser.ts index 9a14bdfd..f438e6b0 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -46,6 +46,13 @@ csiStateHandler['G'] = (handler, params) => handler.cursorCharAbsolute(params); csiStateHandler['H'] = (handler, params) => handler.cursorPosition(params); csiStateHandler['J'] = (handler, params) => handler.eraseInDisplay(params); csiStateHandler['K'] = (handler, params) => handler.eraseInLine(params); +csiStateHandler['L'] = (handler, params) => handler.insertLines(params); +csiStateHandler['M'] = (handler, params) => handler.deleteLines(params); +csiStateHandler['P'] = (handler, params) => handler.deleteChars(params); +csiStateHandler['X'] = (handler, params) => handler.eraseChars(params); +csiStateHandler['`'] = (handler, params) => handler.charPosAbsolute(params); +csiStateHandler['a'] = (handler, params) => handler.HPositionRelative(params); +csiStateHandler['c'] = (handler, params) => handler.sendDeviceAttributes(params); csiStateHandler['m'] = (handler, params) => handler.charAttributes(params); csiStateHandler['n'] = (handler, params) => handler.deviceStatus(params); @@ -543,72 +550,6 @@ export class Parser { switch (ch) { - /** - * Additions - */ - - // CSI Ps E - // Cursor Next Line Ps Times (default = 1) (CNL). - case 'E': - this._terminal.cursorNextLine(this._terminal.params); - break; - - // CSI Ps F - // Cursor Preceding Line Ps Times (default = 1) (CNL). - case 'F': - this._terminal.cursorPrecedingLine(this._terminal.params); - break; - - // CSI Ps G - // Cursor Character Absolute [column] (default = [row,1]) (CHA). - case 'G': - this._terminal.cursorCharAbsolute(this._terminal.params); - break; - - // CSI Ps L - // Insert Ps Line(s) (default = 1) (IL). - case 'L': - this._terminal.insertLines(this._terminal.params); - break; - - // CSI Ps M - // Delete Ps Line(s) (default = 1) (DL). - case 'M': - this._terminal.deleteLines(this._terminal.params); - break; - - // CSI Ps P - // Delete Ps Character(s) (default = 1) (DCH). - case 'P': - this._terminal.deleteChars(this._terminal.params); - break; - - // CSI Ps X - // Erase Ps Character(s) (default = 1) (ECH). - case 'X': - this._terminal.eraseChars(this._terminal.params); - break; - - // CSI Pm ` Character Position Absolute - // [column] (default = [row,1]) (HPA). - case '`': - this._terminal.charPosAbsolute(this._terminal.params); - break; - - // 141 61 a * HPR - - // Horizontal Position Relative - case 'a': - this._terminal.HPositionRelative(this._terminal.params); - break; - - // CSI P s c - // Send Device Attributes (Primary DA). - // CSI > P s c - // Send Device Attributes (Secondary DA) - case 'c': - this._terminal.sendDeviceAttributes(this._terminal.params); - break; - // CSI Pm d // Line Position Absolute [row] (default = [1,column]) (VPA). case 'd': diff --git a/src/xterm.js b/src/xterm.js index 62baf517..c6246157 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -2326,208 +2326,6 @@ Terminal.prototype.tabSet = function() { */ -/** - * 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; - - 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). From c43c3b411ab1726a7e9de45af4a8422c612498cb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 12 Jan 2017 20:51:15 -0800 Subject: [PATCH 18/25] Add more CSI codes --- src/InputHandler.ts | 263 ++++++++++++++++++++++++++++++++++++++++++++ src/Interfaces.ts | 4 + src/Parser.ts | 28 +---- src/xterm.js | 259 ------------------------------------------- 4 files changed, 271 insertions(+), 283 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 8ecc075b..f54d7935 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1,5 +1,6 @@ import { IInputHandler, ITerminal } from './Interfaces'; import { C0 } from './EscapeSequences'; +import { CHARSETS } from './Charsets'; export class InputHandler implements IInputHandler { // TODO: We want to type _terminal when it's pulled into TS @@ -511,6 +512,268 @@ export class InputHandler implements IInputHandler { } } + /** + * 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 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: ^[[ Normal (default). diff --git a/src/Interfaces.ts b/src/Interfaces.ts index e79fe502..f6b21233 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -62,6 +62,10 @@ export interface IInputHandler { /** CSI ` */ charPosAbsolute(params: number[]): void; /** CSI a */ HPositionRelative(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 h */ setMode(params: number[]): void; /** CSI m */ charAttributes(params: number[]): void; /** CSI n */ deviceStatus(params: number[]): void; } diff --git a/src/Parser.ts b/src/Parser.ts index f438e6b0..4f5e7336 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -53,6 +53,10 @@ csiStateHandler['X'] = (handler, params) => handler.eraseChars(params); csiStateHandler['`'] = (handler, params) => handler.charPosAbsolute(params); csiStateHandler['a'] = (handler, params) => handler.HPositionRelative(params); csiStateHandler['c'] = (handler, params) => handler.sendDeviceAttributes(params); +csiStateHandler['d'] = (handler, params) => handler.linePosAbsolute(params); +csiStateHandler['e'] = (handler, params) => handler.VPositionRelative(params); +csiStateHandler['f'] = (handler, params) => handler.HVPosition(params); +csiStateHandler['h'] = (handler, params) => handler.setMode(params); csiStateHandler['m'] = (handler, params) => handler.charAttributes(params); csiStateHandler['n'] = (handler, params) => handler.deviceStatus(params); @@ -550,30 +554,6 @@ export class Parser { switch (ch) { - // CSI Pm d - // Line Position Absolute [row] (default = [1,column]) (VPA). - case 'd': - this._terminal.linePosAbsolute(this._terminal.params); - break; - - // 145 65 e * VPR - Vertical Position Relative - case 'e': - this._terminal.VPositionRelative(this._terminal.params); - break; - - // CSI Ps ; Ps f - // Horizontal and Vertical Position [row;column] (default = - // [1,1]) (HVP). - case 'f': - this._terminal.HVPosition(this._terminal.params); - break; - - // CSI Pm h Set Mode (SM). - // CSI ? Pm h - mouse escape codes, cursor escape codes - case 'h': - this._terminal.setMode(this._terminal.params); - break; - // CSI Pm l Reset Mode (RM). // CSI ? Pm l case 'l': diff --git a/src/xterm.js b/src/xterm.js index c6246157..3edd50aa 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -2326,265 +2326,6 @@ Terminal.prototype.tabSet = function() { */ -/** - * 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, CHARSETS.US); - this.setgCharset(1, CHARSETS.US); - this.setgCharset(2, CHARSETS.US); - this.setgCharset(3, 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). From 9b66208029caadf9c292f8d9f705dcd4f24a069e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 12 Jan 2017 21:33:28 -0800 Subject: [PATCH 19/25] Add more CSI codes --- src/InputHandler.ts | 221 +++++++++++++++++++++++++++++++++++++++++++ src/Interfaces.ts | 6 ++ src/Parser.ts | 36 +------ src/xterm.js | 226 -------------------------------------------- 4 files changed, 232 insertions(+), 257 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index f54d7935..98b85ba3 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -239,6 +239,17 @@ export class InputHandler implements IInputHandler { 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). @@ -774,6 +785,183 @@ export class InputHandler implements IInputHandler { } } + /** + * CSI Pm l Reset Mode (RM). + * Ps = 2 -> 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). @@ -1026,4 +1214,37 @@ export class InputHandler implements IInputHandler { } } + /** + * 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; + } } diff --git a/src/Interfaces.ts b/src/Interfaces.ts index f6b21233..b0f4464f 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -44,6 +44,7 @@ export interface IInputHandler { /** C0 HT */ tab(): void; /** C0 SO */ shiftOut(): void; /** C0 SI */ shiftIn(): void; + /** CSI @ */ insertChars(params); /** CSI A */ cursorUp(params: number[]): void; /** CSI B */ cursorDown(params: number[]): void; @@ -53,6 +54,7 @@ export interface IInputHandler { /** 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; @@ -66,6 +68,10 @@ export interface IInputHandler { /** CSI e */ VPositionRelative(params: number[]): void; /** CSI f */ HVPosition(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 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 index 4f5e7336..89d19344 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -44,6 +44,7 @@ csiStateHandler['E'] = (handler, params) => handler.cursorNextLine(params); csiStateHandler['F'] = (handler, params) => handler.cursorPrecedingLine(params); csiStateHandler['G'] = (handler, params) => handler.cursorCharAbsolute(params); csiStateHandler['H'] = (handler, params) => handler.cursorPosition(params); +csiStateHandler['I'] = (handler, params) => handler.cursorForwardTab(params); csiStateHandler['J'] = (handler, params) => handler.eraseInDisplay(params); csiStateHandler['K'] = (handler, params) => handler.eraseInLine(params); csiStateHandler['L'] = (handler, params) => handler.insertLines(params); @@ -57,8 +58,12 @@ csiStateHandler['d'] = (handler, params) => handler.linePosAbsolute(params); csiStateHandler['e'] = (handler, params) => handler.VPositionRelative(params); csiStateHandler['f'] = (handler, params) => handler.HVPosition(params); csiStateHandler['h'] = (handler, params) => handler.setMode(params); +csiStateHandler['l'] = (handler, params) => handler.resetMode(params); csiStateHandler['m'] = (handler, params) => handler.charAttributes(params); csiStateHandler['n'] = (handler, params) => handler.deviceStatus(params); +csiStateHandler['r'] = (handler, params) => handler.setScrollRegion(params); +csiStateHandler['s'] = (handler, params) => handler.saveCursor(params); +csiStateHandler['u'] = (handler, params) => handler.restoreCursor(params); enum ParserState { NORMAL = 0, @@ -554,42 +559,11 @@ export class Parser { switch (ch) { - // CSI Pm l Reset Mode (RM). - // CSI ? Pm l - case 'l': - this._terminal.resetMode(this._terminal.params); - break; - - // CSI Ps ; Ps r - // Set Scrolling Region [top;bottom] (default = full size of win- - // dow) (DECSTBM). - // CSI ? Pm r - case 'r': - this._terminal.setScrollRegion(this._terminal.params); - break; - - // CSI s - // Save cursor (ANSI.SYS). - case 's': - this._terminal.saveCursor(this._terminal.params); - break; - - // CSI u - // Restore cursor (ANSI.SYS). - case 'u': - this._terminal.restoreCursor(this._terminal.params); - break; /** * Lesser Used */ - // CSI Ps I - // Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). - case 'I': - this._terminal.cursorForwardTab(this._terminal.params); - break; - // CSI Ps S Scroll up Ps lines (default = 1) (SU). case 'S': this._terminal.scrollUp(this._terminal.params); diff --git a/src/xterm.js b/src/xterm.js index 3edd50aa..30cb7286 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -2326,236 +2326,10 @@ Terminal.prototype.tabSet = function() { */ -/** - * CSI Pm l Reset Mode (RM). - * Ps = 2 -> 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(); - } -}; /** From f4846aa1545834a7559f9085cec42c83971b4fa9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 12 Jan 2017 21:58:50 -0800 Subject: [PATCH 20/25] Finish CSI codes --- src/InputHandler.ts | 88 +++++++++ src/Interfaces.ts | 64 +++--- src/Parser.ts | 324 +++++-------------------------- src/xterm.js | 464 -------------------------------------------- 4 files changed, 170 insertions(+), 770 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 98b85ba3..42c84769 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -405,6 +405,34 @@ export class InputHandler implements IInputHandler { } } + /** + * 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). @@ -426,6 +454,16 @@ export class InputHandler implements IInputHandler { } } + /** + * 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). @@ -457,6 +495,19 @@ export class InputHandler implements IInputHandler { } } + /** + * CSI Ps b Repeat the preceding graphic character Ps times (REP). + */ + public repeatPrecedingCharacter(params: number[]): void { + let param = params[0] || 1 + , line = this._terminal.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 @@ -574,6 +625,22 @@ export class InputHandler implements IInputHandler { } } + /** + * 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). @@ -1214,6 +1281,27 @@ export class InputHandler implements IInputHandler { } } + /** + * 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- diff --git a/src/Interfaces.ts b/src/Interfaces.ts index b0f4464f..0079b856 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -45,33 +45,39 @@ export interface IInputHandler { /** C0 SO */ shiftOut(): void; /** C0 SI */ shiftIn(): void; - /** CSI @ */ insertChars(params); - /** 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 X */ eraseChars(params: number[]): void; - /** CSI ` */ charPosAbsolute(params: number[]): void; - /** CSI a */ HPositionRelative(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 h */ setMode(params: number[]): void; - /** CSI l */ resetMode(params: number[]): void; - /** CSI m */ charAttributes(params: number[]): void; - /** CSI n */ deviceStatus(params: number[]): void; - /** CSI r */ setScrollRegion(params: number[]): void; - /** CSI s */ saveCursor(params: number[]): void; - /** CSI u */ restoreCursor(params: number[]): 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 index 89d19344..42554117 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -34,33 +34,47 @@ csiParamStateHandler[' '] = (parser) => parser.setPostfix(' '); csiParamStateHandler['\''] = (parser) => parser.setPostfix('\''); csiParamStateHandler[';'] = (parser) => parser.finalizeParam(); -const csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[]) => void} = {}; -csiStateHandler['@'] = (handler, params) => handler.insertChars(params); -csiStateHandler['A'] = (handler, params) => handler.cursorUp(params); -csiStateHandler['B'] = (handler, params) => handler.cursorDown(params); -csiStateHandler['C'] = (handler, params) => handler.cursorForward(params); -csiStateHandler['D'] = (handler, params) => handler.cursorBackward(params); -csiStateHandler['E'] = (handler, params) => handler.cursorNextLine(params); -csiStateHandler['F'] = (handler, params) => handler.cursorPrecedingLine(params); -csiStateHandler['G'] = (handler, params) => handler.cursorCharAbsolute(params); -csiStateHandler['H'] = (handler, params) => handler.cursorPosition(params); -csiStateHandler['I'] = (handler, params) => handler.cursorForwardTab(params); -csiStateHandler['J'] = (handler, params) => handler.eraseInDisplay(params); -csiStateHandler['K'] = (handler, params) => handler.eraseInLine(params); -csiStateHandler['L'] = (handler, params) => handler.insertLines(params); -csiStateHandler['M'] = (handler, params) => handler.deleteLines(params); -csiStateHandler['P'] = (handler, params) => handler.deleteChars(params); -csiStateHandler['X'] = (handler, params) => handler.eraseChars(params); -csiStateHandler['`'] = (handler, params) => handler.charPosAbsolute(params); -csiStateHandler['a'] = (handler, params) => handler.HPositionRelative(params); -csiStateHandler['c'] = (handler, params) => handler.sendDeviceAttributes(params); -csiStateHandler['d'] = (handler, params) => handler.linePosAbsolute(params); -csiStateHandler['e'] = (handler, params) => handler.VPositionRelative(params); -csiStateHandler['f'] = (handler, params) => handler.HVPosition(params); -csiStateHandler['h'] = (handler, params) => handler.setMode(params); -csiStateHandler['l'] = (handler, params) => handler.resetMode(params); -csiStateHandler['m'] = (handler, params) => handler.charAttributes(params); -csiStateHandler['n'] = (handler, params) => handler.deviceStatus(params); +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); @@ -355,13 +369,13 @@ export class Parser { // ESC 7 Save Cursor (DECSC). case '7': - this._terminal.saveCursor(); + this._inputHandler.saveCursor(); this.state = ParserState.NORMAL; break; // ESC 8 Restore Cursor (DECRC). case '8': - this._terminal.restoreCursor(); + this._inputHandler.restoreCursor(); this.state = ParserState.NORMAL; break; @@ -549,257 +563,13 @@ export class Parser { this.state = ParserState.CSI; case ParserState.CSI: - this.state = ParserState.NORMAL; - if (ch in csiStateHandler) { - csiStateHandler[ch](this._inputHandler, this._terminal.params); - // Skip below switch as this has handled these codes (eventually everything will be handled here - break; - } - - switch (ch) { - - - /** - * Lesser Used - */ - - // CSI Ps S Scroll up Ps lines (default = 1) (SU). - case 'S': - this._terminal.scrollUp(this._terminal.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._terminal.params.length < 2 && !this._terminal.prefix) { - this._terminal.scrollDown(this._terminal.params); - } - break; - - // CSI Ps Z - // Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). - case 'Z': - this._terminal.cursorBackwardTab(this._terminal.params); - break; - - // CSI Ps b Repeat the preceding graphic character Ps times (REP). - case 'b': - this._terminal.repeatPrecedingCharacter(this._terminal.params); - break; - - // CSI Ps g Tab Clear (TBC). - case 'g': - this._terminal.tabClear(this._terminal.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._terminal.prefix) { - // case '>': - // this.setPointerMode(this.params); - // break; - case '!': - this._terminal.softReset(this._terminal.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._terminal.error('Unknown CSI code: %s.', ch); - break; + 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; diff --git a/src/xterm.js b/src/xterm.js index 30cb7286..b3ecdf8d 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -2315,470 +2315,6 @@ Terminal.prototype.tabSet = function() { this.tabs[this.x] = true; }; - -/** - * CSI - */ - - -/** - * Additions - */ - - -/** - * Lesser Used - */ - - - -/** - * 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(); -}; - /** * Helpers */ From fa3484cd14921a0b7dad46d764910cdfb59662bf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 12 Jan 2017 22:17:36 -0800 Subject: [PATCH 21/25] Pull character add char code to input handler --- src/InputHandler.ts | 184 ++++++++++++++++++++++++++++++++++++ src/Interfaces.ts | 2 + src/Parser.ts | 225 ++++---------------------------------------- 3 files changed, 204 insertions(+), 207 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 42c84769..d13ad427 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -6,6 +6,85 @@ 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). @@ -1336,3 +1415,108 @@ export class InputHandler implements IInputHandler { 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 0079b856..87809286 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -37,6 +37,8 @@ export interface ITerminal { * 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; diff --git a/src/Parser.ts b/src/Parser.ts index 42554117..95ecf754 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -2,17 +2,17 @@ import { C0 } from './EscapeSequences'; import { IInputHandler } from './Interfaces'; import { CHARSETS } from './Charsets'; -const normalStateHandler: {[key: string]: (handler: IInputHandler) => void} = {}; -normalStateHandler[C0.BEL] = (handler) => handler.bell(); -normalStateHandler[C0.LF] = (handler) => handler.lineFeed(); +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] = (handler) => handler.carriageReturn(); -normalStateHandler[C0.BS] = (handler) => handler.backspace(); -normalStateHandler[C0.HT] = (handler) => handler.tab(); -normalStateHandler[C0.SO] = (handler) => handler.shiftOut(); -normalStateHandler[C0.SI] = (handler) => handler.shiftIn(); -// TODO: Add ESC and Default cases to normalStateHandler +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); const csiParamStateHandler: {[key: string]: (parser: Parser) => void} = {}; csiParamStateHandler['?'] = (parser) => parser.setPrefix('?'); @@ -102,7 +102,7 @@ export class Parser { } public parse(data: string) { - let l = data.length, i = 0, j, cs, ch, code, low, ch_width, row; + let l = data.length, i = 0, j, cs, ch, code, low; // apply leftover surrogate high from last write if (this._terminal.surrogate_high) { @@ -132,100 +132,12 @@ export class Parser { if (0xDC00 <= code && code <= 0xDFFF) continue; - if (this.state === ParserState.NORMAL) { - if (ch in normalStateHandler) { - normalStateHandler[ch](this._inputHandler); - // Skip switch statement (eventually everything will be handled this way - continue; - } - } - switch (this.state) { case ParserState.NORMAL: - switch (ch) { - case C0.ESC: - this.state = ParserState.ESCAPED; - break; - - default: - // ' ' - // calculate print space - // expensive call, therefore we save width in line buffer - ch_width = wcwidth(code); - - if (ch >= ' ') { - if (this._terminal.charset && this._terminal.charset[ch]) { - ch = this._terminal.charset[ch]; - } - - 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] += ch; - - } else { - this._terminal.lines.get(row)[this._terminal.x - 1][1] += ch; - } - this._terminal.updateRange(this._terminal.y); - } - break; - } - - // 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 - continue; - } - } - 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, ch, 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++; - } - } - break; + if (ch in normalStateHandler) { + normalStateHandler[ch](this, this._inputHandler); + } else { + this._inputHandler.addChar(ch, code); } break; case ParserState.ESCAPED: @@ -671,6 +583,10 @@ export class Parser { } } + public setState(state: ParserState): void { + this.state = state; + } + public setPrefix(prefix: string): void { this._terminal.prefix = prefix; } @@ -692,108 +608,3 @@ export class Parser { this._terminal.postfix = postfix; } } - -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 From ecd7e69f1a0be3c0e072112344d33d96ad8f5027 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 12 Jan 2017 22:36:48 -0800 Subject: [PATCH 22/25] Start adding ESC state handler --- src/Parser.ts | 195 ++++++++++++++++++++++++++------------------------ 1 file changed, 103 insertions(+), 92 deletions(-) diff --git a/src/Parser.ts b/src/Parser.ts index 95ecf754..5dab9cef 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -14,6 +14,63 @@ 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('>'); @@ -91,48 +148,50 @@ enum ParserState { } export class Parser { - private state: ParserState; + 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; + this._state = ParserState.NORMAL; } public parse(data: string) { - let l = data.length, i = 0, j, cs, ch, code, low; + 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 (; i < l; i++) { - ch = data[i]; + 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(i); + code = data.charCodeAt(this._position); if (0xD800 <= code && code <= 0xDBFF) { // we got a surrogate high // get surrogate low (next 2 bytes) - low = data.charCodeAt(i + 1); + 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(i + 1); + ch += data.charAt(this._position + 1); } // surrogate low - already handled above if (0xDC00 <= code && code <= 0xDFFF) continue; - switch (this.state) { + switch (this._state) { case ParserState.NORMAL: if (ch in normalStateHandler) { normalStateHandler[ch](this, this._inputHandler); @@ -141,68 +200,12 @@ export class Parser { } break; case ParserState.ESCAPED: + if (ch in escapedStateHandler) { + escapedStateHandler[ch](this, this._terminal); + // Skip switch as it was just handled + break; + } switch (ch) { - // ESC [ Control Sequence Introducer ( CSI is 0x9b). - case '[': - this._terminal.params = []; - this._terminal.currentParam = 0; - this.state = ParserState.CSI_PARAM; - break; - - // ESC ] Operating System Command ( OSC is 0x9d). - case ']': - this._terminal.params = []; - this._terminal.currentParam = 0; - this.state = ParserState.OSC; - break; - - // ESC P Device Control String ( DCS is 0x90). - case 'P': - this._terminal.params = []; - this._terminal.currentParam = 0; - this.state = ParserState.DCS; - break; - - // ESC _ Application Program Command ( APC is 0x9f). - case '_': - this.state = ParserState.IGNORE; - break; - - // ESC ^ Privacy Message ( PM is 0x9e). - case '^': - this.state = ParserState.IGNORE; - break; - - // ESC c Full Reset (RIS). - case 'c': - this._terminal.reset(); - break; - - // ESC E Next Line ( NEL is 0x85). - // ESC D Index ( IND is 0x84). - case 'E': - this._terminal.x = 0; - ; - case 'D': - this._terminal.index(); - this.state = ParserState.NORMAL; - break; - - // ESC M Reverse Index ( RI is 0x8d). - case 'M': - this._terminal.reverseIndex(); - this.state = ParserState.NORMAL; - break; - - // ESC % Select default/utf-8 character set. - // @ = default, G = utf-8 - case '%': - // this.charset = null; - this._terminal.setgLevel(0); - this._terminal.setgCharset(0, CHARSETS.US); - this.state = ParserState.NORMAL; - i++; - break; // ESC (,),*,+,-,. Designate G0-G2 Character Set. case '(': // <-- this seems to get all the attention @@ -231,7 +234,7 @@ export class Parser { this._terminal.gcharset = 2; break; } - this.state = ParserState.CHARSET; + this._state = ParserState.CHARSET; break; // Designate G3 Character Set (VT300). @@ -239,8 +242,8 @@ export class Parser { // Not implemented. case '/': this._terminal.gcharset = 3; - this.state = ParserState.CHARSET; - i--; + this._state = ParserState.CHARSET; + this._position--; break; // ESC N @@ -282,25 +285,25 @@ export class Parser { // ESC 7 Save Cursor (DECSC). case '7': this._inputHandler.saveCursor(); - this.state = ParserState.NORMAL; + this._state = ParserState.NORMAL; break; // ESC 8 Restore Cursor (DECRC). case '8': this._inputHandler.restoreCursor(); - this.state = ParserState.NORMAL; + this._state = ParserState.NORMAL; break; // ESC # 3 DEC line height/width case '#': - this.state = ParserState.NORMAL; - i++; + this._state = ParserState.NORMAL; + this._position++; break; // ESC H Tab Set (HTS is 0x88). case 'H': this._terminal.tabSet(); - this.state = ParserState.NORMAL; + this._state = ParserState.NORMAL; break; // ESC = Application Keypad (DECKPAM). @@ -308,7 +311,7 @@ export class Parser { this._terminal.log('Serial port requested application keypad.'); this._terminal.applicationKeypad = true; this._terminal.viewport.syncScrollArea(); - this.state = ParserState.NORMAL; + this._state = ParserState.NORMAL; break; // ESC > Normal Keypad (DECKPNM). @@ -316,11 +319,11 @@ export class Parser { this._terminal.log('Switching back to normal keypad.'); this._terminal.applicationKeypad = false; this._terminal.viewport.syncScrollArea(); - this.state = ParserState.NORMAL; + this._state = ParserState.NORMAL; break; default: - this.state = ParserState.NORMAL; + this._state = ParserState.NORMAL; this._terminal.error('Unknown ESC control: %s.', ch); break; } @@ -372,7 +375,7 @@ export class Parser { break; case '/': // ISOLatin (actually /A) cs = CHARSETS.ISOLatin; - i++; + this._position++; break; default: // Default cs = CHARSETS.US; @@ -380,7 +383,7 @@ export class Parser { } this._terminal.setgCharset(this._terminal.gcharset, cs); this._terminal.gcharset = null; - this.state = ParserState.NORMAL; + this._state = ParserState.NORMAL; break; case ParserState.OSC: @@ -388,7 +391,7 @@ export class Parser { // OSC Ps ; Pt BEL // Set Text Parameters. if (ch === C0.ESC || ch === C0.BEL) { - if (ch === C0.ESC) i++; + if (ch === C0.ESC) this._position++; this._terminal.params.push(this._terminal.currentParam); @@ -449,7 +452,7 @@ export class Parser { this._terminal.params = []; this._terminal.currentParam = 0; - this.state = ParserState.NORMAL; + this._state = ParserState.NORMAL; } else { if (!this._terminal.params.length) { if (ch >= '0' && ch <= '9') { @@ -472,7 +475,7 @@ export class Parser { } this.finalizeParam(); // Fall through the CSI as this character should be the CSI code. - this.state = ParserState.CSI; + this._state = ParserState.CSI; case ParserState.CSI: if (ch in csiStateHandler) { @@ -481,14 +484,14 @@ export class Parser { this._terminal.error('Unknown CSI code: %s.', ch); } - this.state = ParserState.NORMAL; + 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) i++; + if (ch === C0.ESC) this._position++; switch (this._terminal.prefix) { // User-Defined Keys (DECUDK). @@ -558,7 +561,7 @@ export class Parser { this._terminal.currentParam = 0; this._terminal.prefix = ''; - this.state = ParserState.NORMAL; + this._state = ParserState.NORMAL; } else if (!this._terminal.currentParam) { if (!this._terminal.prefix && ch !== '$' && ch !== '+') { this._terminal.currentParam = ch; @@ -575,8 +578,8 @@ export class Parser { case ParserState.IGNORE: // For PM and APC. if (ch === C0.ESC || ch === C0.BEL) { - if (ch === C0.ESC) i++; - this.state = ParserState.NORMAL; + if (ch === C0.ESC) this._position++; + this._state = ParserState.NORMAL; } break; } @@ -584,7 +587,7 @@ export class Parser { } public setState(state: ParserState): void { - this.state = state; + this._state = state; } public setPrefix(prefix: string): void { @@ -607,4 +610,12 @@ export class Parser { public setPostfix(postfix: string): void { this._terminal.postfix = postfix; } + + public skipNextChar(): void { + this._position++; + } + + // public repeatChar(): void { + // this._position--; + // } } From 4448ebe391744381dbac0fa7a4045ad99813ef1e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 14 Jan 2017 22:06:30 -0800 Subject: [PATCH 23/25] Use charsets to map --- src/Parser.ts | 87 +++++++++++++++++++-------------------------------- 1 file changed, 33 insertions(+), 54 deletions(-) diff --git a/src/Parser.ts b/src/Parser.ts index 5dab9cef..b429c570 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -37,21 +37,21 @@ escapedStateHandler['P'] = (parser, terminal) => { 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(); @@ -136,6 +136,28 @@ 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, @@ -330,56 +352,13 @@ export class Parser { break; case ParserState.CHARSET: - switch (ch) { - case '0': // DEC Special Character and Line Drawing Set. - cs = CHARSETS.SCLD; - break; - case 'A': // UK - cs = CHARSETS.UK; - break; - case 'B': // United States (USASCII). - cs = CHARSETS.US; - break; - case '4': // Dutch - cs = CHARSETS.Dutch; - break; - case 'C': // Finnish - case '5': - cs = CHARSETS.Finnish; - break; - case 'R': // French - cs = CHARSETS.French; - break; - case 'Q': // FrenchCanadian - cs = CHARSETS.FrenchCanadian; - break; - case 'K': // German - cs = CHARSETS.German; - break; - case 'Y': // Italian - cs = CHARSETS.Italian; - break; - case 'E': // NorwegianDanish - case '6': - cs = CHARSETS.NorwegianDanish; - break; - case 'Z': // Spanish - cs = CHARSETS.Spanish; - break; - case 'H': // Swedish - case '7': - cs = CHARSETS.Swedish; - break; - case '=': // Swiss - cs = CHARSETS.Swiss; - break; - case '/': // ISOLatin (actually /A) - cs = CHARSETS.ISOLatin; - this._position++; - break; - default: // Default - cs = CHARSETS.US; - break; + 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; From 1cdd2e00ab70180126dfa1c813c62bd0a895e047 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 16 Jan 2017 14:11:00 -0800 Subject: [PATCH 24/25] Remove parser states from xterm.js --- src/xterm.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index 40de418a..24a840c0 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -37,11 +37,6 @@ import { CHARSETS } from './Charsets'; // 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 From ef60e50e1decaaf79ca0fb1f988f1a1c5aa0c1b7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Jan 2017 09:35:27 -0800 Subject: [PATCH 25/25] Add documentation --- src/Charsets.ts | 11 ++++++++- src/InputHandler.ts | 11 +++++++++ src/Parser.ts | 58 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/src/Charsets.ts b/src/Charsets.ts index 4035d453..3220f7d5 100644 --- a/src/Charsets.ts +++ b/src/Charsets.ts @@ -1,4 +1,13 @@ -// TODO: Give this a proper type +/** + * @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. diff --git a/src/InputHandler.ts b/src/InputHandler.ts index d13ad427..00626ceb 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1,7 +1,18 @@ +/** + * @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) { } diff --git a/src/Parser.ts b/src/Parser.ts index b429c570..860baaa6 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -1,3 +1,7 @@ +/** + * @license MIT + */ + import { C0 } from './EscapeSequences'; import { IInputHandler } from './Interfaces'; import { CHARSETS } from './Charsets'; @@ -169,6 +173,11 @@ enum ParserState { 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; @@ -181,6 +190,11 @@ export class Parser { 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; @@ -565,35 +579,71 @@ export class Parser { } } + /** + * 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; } - public setPostfix(postfix: string): void { - this._terminal.postfix = postfix; - } - + /** + * 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--; // }