5 import { C0 } from './EscapeSequences';
6 import { IInputHandler } from './Interfaces';
7 import { CHARSETS, DEFAULT_CHARSET } from './Charsets';
9 const normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {};
10 normalStateHandler[C0.BEL] = (parser, handler) => handler.bell();
11 normalStateHandler[C0.LF] = (parser, handler) => handler.lineFeed();
12 normalStateHandler[C0.VT] = normalStateHandler[C0.LF];
13 normalStateHandler[C0.FF] = normalStateHandler[C0.LF];
14 normalStateHandler[C0.CR] = (parser, handler) => handler.carriageReturn();
15 normalStateHandler[C0.BS] = (parser, handler) => handler.backspace();
16 normalStateHandler[C0.HT] = (parser, handler) => handler.tab();
17 normalStateHandler[C0.SO] = (parser, handler) => handler.shiftOut();
18 normalStateHandler[C0.SI] = (parser, handler) => handler.shiftIn();
19 normalStateHandler[C0.ESC] = (parser, handler) => parser.setState(ParserState.ESCAPED);
21 // TODO: Remove terminal when parser owns params and currentParam
22 const escapedStateHandler: {[key: string]: (parser: Parser, terminal: any) => void} = {};
23 escapedStateHandler['['] = (parser, terminal) => {
24 // ESC [ Control Sequence Introducer (CSI is 0x9b)
26 terminal.currentParam = 0;
27 parser.setState(ParserState.CSI_PARAM);
29 escapedStateHandler[']'] = (parser, terminal) => {
30 // ESC ] Operating System Command (OSC is 0x9d)
32 terminal.currentParam = 0;
33 parser.setState(ParserState.OSC);
35 escapedStateHandler['P'] = (parser, terminal) => {
36 // ESC P Device Control String (DCS is 0x90)
38 terminal.currentParam = 0;
39 parser.setState(ParserState.DCS);
41 escapedStateHandler['_'] = (parser, terminal) => {
42 // ESC _ Application Program Command ( APC is 0x9f).
43 parser.setState(ParserState.IGNORE);
45 escapedStateHandler['^'] = (parser, terminal) => {
46 // ESC ^ Privacy Message ( PM is 0x9e).
47 parser.setState(ParserState.IGNORE);
49 escapedStateHandler['c'] = (parser, terminal) => {
50 // ESC c Full Reset (RIS).
53 escapedStateHandler['E'] = (parser, terminal) => {
54 // ESC E Next Line ( NEL is 0x85).
55 terminal.buffer.x = 0;
57 parser.setState(ParserState.NORMAL);
59 escapedStateHandler['D'] = (parser, terminal) => {
60 // ESC D Index ( IND is 0x84).
62 parser.setState(ParserState.NORMAL);
64 escapedStateHandler['M'] = (parser, terminal) => {
65 // ESC M Reverse Index ( RI is 0x8d).
66 terminal.reverseIndex();
67 parser.setState(ParserState.NORMAL);
69 escapedStateHandler['%'] = (parser, terminal) => {
70 // ESC % Select default/utf-8 character set.
71 // @ = default, G = utf-8
72 terminal.setgLevel(0);
73 terminal.setgCharset(0, DEFAULT_CHARSET); // US (default)
74 parser.setState(ParserState.NORMAL);
75 parser.skipNextChar();
77 escapedStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);
79 const csiParamStateHandler: {[key: string]: (parser: Parser) => void} = {};
80 csiParamStateHandler['?'] = (parser) => parser.setPrefix('?');
81 csiParamStateHandler['>'] = (parser) => parser.setPrefix('>');
82 csiParamStateHandler['!'] = (parser) => parser.setPrefix('!');
83 csiParamStateHandler['0'] = (parser) => parser.setParam(parser.getParam() * 10);
84 csiParamStateHandler['1'] = (parser) => parser.setParam(parser.getParam() * 10 + 1);
85 csiParamStateHandler['2'] = (parser) => parser.setParam(parser.getParam() * 10 + 2);
86 csiParamStateHandler['3'] = (parser) => parser.setParam(parser.getParam() * 10 + 3);
87 csiParamStateHandler['4'] = (parser) => parser.setParam(parser.getParam() * 10 + 4);
88 csiParamStateHandler['5'] = (parser) => parser.setParam(parser.getParam() * 10 + 5);
89 csiParamStateHandler['6'] = (parser) => parser.setParam(parser.getParam() * 10 + 6);
90 csiParamStateHandler['7'] = (parser) => parser.setParam(parser.getParam() * 10 + 7);
91 csiParamStateHandler['8'] = (parser) => parser.setParam(parser.getParam() * 10 + 8);
92 csiParamStateHandler['9'] = (parser) => parser.setParam(parser.getParam() * 10 + 9);
93 csiParamStateHandler['$'] = (parser) => parser.setPostfix('$');
94 csiParamStateHandler['"'] = (parser) => parser.setPostfix('"');
95 csiParamStateHandler[' '] = (parser) => parser.setPostfix(' ');
96 csiParamStateHandler['\''] = (parser) => parser.setPostfix('\'');
97 csiParamStateHandler[';'] = (parser) => parser.finalizeParam();
98 csiParamStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);
100 const csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[], prefix: string, postfix: string, parser: Parser) => void} = {};
101 csiStateHandler['@'] = (handler, params, prefix) => handler.insertChars(params);
102 csiStateHandler['A'] = (handler, params, prefix) => handler.cursorUp(params);
103 csiStateHandler['B'] = (handler, params, prefix) => handler.cursorDown(params);
104 csiStateHandler['C'] = (handler, params, prefix) => handler.cursorForward(params);
105 csiStateHandler['D'] = (handler, params, prefix) => handler.cursorBackward(params);
106 csiStateHandler['E'] = (handler, params, prefix) => handler.cursorNextLine(params);
107 csiStateHandler['F'] = (handler, params, prefix) => handler.cursorPrecedingLine(params);
108 csiStateHandler['G'] = (handler, params, prefix) => handler.cursorCharAbsolute(params);
109 csiStateHandler['H'] = (handler, params, prefix) => handler.cursorPosition(params);
110 csiStateHandler['I'] = (handler, params, prefix) => handler.cursorForwardTab(params);
111 csiStateHandler['J'] = (handler, params, prefix) => handler.eraseInDisplay(params);
112 csiStateHandler['K'] = (handler, params, prefix) => handler.eraseInLine(params);
113 csiStateHandler['L'] = (handler, params, prefix) => handler.insertLines(params);
114 csiStateHandler['M'] = (handler, params, prefix) => handler.deleteLines(params);
115 csiStateHandler['P'] = (handler, params, prefix) => handler.deleteChars(params);
116 csiStateHandler['S'] = (handler, params, prefix) => handler.scrollUp(params);
117 csiStateHandler['T'] = (handler, params, prefix) => {
118 if (params.length < 2 && !prefix) {
119 handler.scrollDown(params);
122 csiStateHandler['X'] = (handler, params, prefix) => handler.eraseChars(params);
123 csiStateHandler['Z'] = (handler, params, prefix) => handler.cursorBackwardTab(params);
124 csiStateHandler['`'] = (handler, params, prefix) => handler.charPosAbsolute(params);
125 csiStateHandler['a'] = (handler, params, prefix) => handler.HPositionRelative(params);
126 csiStateHandler['b'] = (handler, params, prefix) => handler.repeatPrecedingCharacter(params);
127 csiStateHandler['c'] = (handler, params, prefix) => handler.sendDeviceAttributes(params);
128 csiStateHandler['d'] = (handler, params, prefix) => handler.linePosAbsolute(params);
129 csiStateHandler['e'] = (handler, params, prefix) => handler.VPositionRelative(params);
130 csiStateHandler['f'] = (handler, params, prefix) => handler.HVPosition(params);
131 csiStateHandler['g'] = (handler, params, prefix) => handler.tabClear(params);
132 csiStateHandler['h'] = (handler, params, prefix) => handler.setMode(params);
133 csiStateHandler['l'] = (handler, params, prefix) => handler.resetMode(params);
134 csiStateHandler['m'] = (handler, params, prefix) => handler.charAttributes(params);
135 csiStateHandler['n'] = (handler, params, prefix) => handler.deviceStatus(params);
136 csiStateHandler['p'] = (handler, params, prefix) => {
138 case '!': handler.softReset(params); break;
141 csiStateHandler['q'] = (handler, params, prefix, postfix) => {
142 if (postfix === ' ') {
143 handler.setCursorStyle(params);
146 csiStateHandler['r'] = (handler, params) => handler.setScrollRegion(params);
147 csiStateHandler['s'] = (handler, params) => handler.saveCursor(params);
148 csiStateHandler['u'] = (handler, params) => handler.restoreCursor(params);
149 csiStateHandler[C0.CAN] = (handler, params, prefix, postfix, parser) => parser.setState(ParserState.NORMAL);
163 * The terminal's parser, all input into the terminal goes through the parser
164 * which parses and defers the actual input handling the the IInputHandler
165 * specified in the constructor.
167 export class Parser {
168 private _state: ParserState;
169 private _position: number;
171 // TODO: Remove terminal when handler can do everything
173 private _inputHandler: IInputHandler,
174 private _terminal: any
176 this._state = ParserState.NORMAL;
180 * Parse and handle data.
182 * @param data The data to parse.
184 public parse(data: string): ParserState {
185 let l = data.length, j, cs, ch, code, low;
187 if (this._terminal.debug) {
188 this._terminal.log('data: ' + data);
192 // apply leftover surrogate high from last write
193 if (this._terminal.surrogate_high) {
194 data = this._terminal.surrogate_high + data;
195 this._terminal.surrogate_high = '';
198 for (; this._position < l; this._position++) {
199 ch = data[this._position];
201 // FIXME: higher chars than 0xa0 are not allowed in escape sequences
202 // --> maybe move to default
203 code = data.charCodeAt(this._position);
204 if (0xD800 <= code && code <= 0xDBFF) {
205 // we got a surrogate high
206 // get surrogate low (next 2 bytes)
207 low = data.charCodeAt(this._position + 1);
209 // end of data stream, save surrogate high
210 this._terminal.surrogate_high = ch;
213 code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
214 ch += data.charAt(this._position + 1);
216 // surrogate low - already handled above
217 if (0xDC00 <= code && code <= 0xDFFF)
220 switch (this._state) {
221 case ParserState.NORMAL:
222 if (ch in normalStateHandler) {
223 normalStateHandler[ch](this, this._inputHandler);
225 this._inputHandler.addChar(ch, code);
228 case ParserState.ESCAPED:
229 if (ch in escapedStateHandler) {
230 escapedStateHandler[ch](this, this._terminal);
231 // Skip switch as it was just handled
236 // ESC (,),*,+,-,. Designate G0-G2 Character Set.
237 case '(': // <-- this seems to get all the attention
245 this._terminal.gcharset = 0;
248 this._terminal.gcharset = 1;
251 this._terminal.gcharset = 2;
254 this._terminal.gcharset = 3;
257 this._terminal.gcharset = 1;
260 this._terminal.gcharset = 2;
263 this._state = ParserState.CHARSET;
266 // Designate G3 Character Set (VT300).
267 // A = ISO Latin-1 Supplemental.
270 this._terminal.gcharset = 3;
271 this._state = ParserState.CHARSET;
276 // Single Shift Select of G2 Character Set
277 // ( SS2 is 0x8e). This affects next character only.
281 // Single Shift Select of G3 Character Set
282 // ( SS3 is 0x8f). This affects next character only.
286 // Invoke the G2 Character Set as GL (LS2).
288 this._terminal.setgLevel(2);
291 // Invoke the G3 Character Set as GL (LS3).
293 this._terminal.setgLevel(3);
296 // Invoke the G3 Character Set as GR (LS3R).
298 this._terminal.setgLevel(3);
301 // Invoke the G2 Character Set as GR (LS2R).
303 this._terminal.setgLevel(2);
306 // Invoke the G1 Character Set as GR (LS1R).
308 this._terminal.setgLevel(1);
311 // ESC 7 Save Cursor (DECSC).
313 this._inputHandler.saveCursor();
314 this._state = ParserState.NORMAL;
317 // ESC 8 Restore Cursor (DECRC).
319 this._inputHandler.restoreCursor();
320 this._state = ParserState.NORMAL;
323 // ESC # 3 DEC line height/width
325 this._state = ParserState.NORMAL;
329 // ESC H Tab Set (HTS is 0x88).
331 this._terminal.tabSet();
332 this._state = ParserState.NORMAL;
335 // ESC = Application Keypad (DECKPAM).
337 this._terminal.log('Serial port requested application keypad.');
338 this._terminal.applicationKeypad = true;
339 this._terminal.viewport.syncScrollArea();
340 this._state = ParserState.NORMAL;
343 // ESC > Normal Keypad (DECKPNM).
345 this._terminal.log('Switching back to normal keypad.');
346 this._terminal.applicationKeypad = false;
347 this._terminal.viewport.syncScrollArea();
348 this._state = ParserState.NORMAL;
352 this._state = ParserState.NORMAL;
353 this._terminal.error('Unknown ESC control: %s.', ch);
358 case ParserState.CHARSET:
359 if (ch in CHARSETS) {
361 if (ch === '/') { // ISOLatin is actually /A
365 cs = DEFAULT_CHARSET;
367 this._terminal.setgCharset(this._terminal.gcharset, cs);
368 this._terminal.gcharset = null;
369 this._state = ParserState.NORMAL;
372 case ParserState.OSC:
375 // Set Text Parameters.
376 if (ch === C0.ESC || ch === C0.BEL) {
377 if (ch === C0.ESC) this._position++;
379 this._terminal.params.push(this._terminal.currentParam);
381 switch (this._terminal.params[0]) {
385 if (this._terminal.params[1]) {
386 this._terminal.title = this._terminal.params[1];
387 this._terminal.handleTitle(this._terminal.title);
395 // change dynamic colors
407 // change dynamic ui colors
419 // manipulate selection data
436 this._terminal.params = [];
437 this._terminal.currentParam = 0;
438 this._state = ParserState.NORMAL;
440 if (!this._terminal.params.length) {
441 if (ch >= '0' && ch <= '9') {
442 this._terminal.currentParam =
443 this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48;
444 } else if (ch === ';') {
445 this._terminal.params.push(this._terminal.currentParam);
446 this._terminal.currentParam = '';
449 this._terminal.currentParam += ch;
454 case ParserState.CSI_PARAM:
455 if (ch in csiParamStateHandler) {
456 csiParamStateHandler[ch](this);
459 this.finalizeParam();
460 // Fall through the CSI as this character should be the CSI code.
461 this._state = ParserState.CSI;
463 case ParserState.CSI:
464 if (ch in csiStateHandler) {
465 if (this._terminal.debug) {
466 this._terminal.log(`CSI ${this._terminal.prefix ? this._terminal.prefix : ''} ${this._terminal.params ? this._terminal.params.join(';') : ''} ${this._terminal.postfix ? this._terminal.postfix : ''} ${ch}`);
468 csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this);
470 this._terminal.error('Unknown CSI code: %s.', ch);
473 this._state = ParserState.NORMAL;
474 this._terminal.prefix = '';
475 this._terminal.postfix = '';
478 case ParserState.DCS:
479 if (ch === C0.ESC || ch === C0.BEL) {
480 if (ch === C0.ESC) this._position++;
484 switch (this._terminal.prefix) {
485 // User-Defined Keys (DECUDK).
489 // Request Status String (DECRQSS).
490 // test: echo -e '\eP$q"p\e\\'
492 pt = this._terminal.currentParam;
509 + (this._terminal.buffer.scrollTop + 1)
511 + (this._terminal.buffer.scrollBottom + 1)
521 this._terminal.error('Unknown DCS Pt: %s.', pt);
526 this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\');
529 // Set Termcap/Terminfo Data (xterm, experimental).
533 // Request Termcap/Terminfo String (xterm, experimental)
534 // Regular xterm does not even respond to this sequence.
535 // This can cause a small glitch in vim.
536 // test: echo -ne '\eP+q6b64\e\\'
538 pt = this._terminal.currentParam;
541 this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\');
545 this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix);
549 this._terminal.currentParam = 0;
550 this._terminal.prefix = '';
551 this._state = ParserState.NORMAL;
552 } else if (!this._terminal.currentParam) {
553 if (!this._terminal.prefix && ch !== '$' && ch !== '+') {
554 this._terminal.currentParam = ch;
555 } else if (this._terminal.prefix.length === 2) {
556 this._terminal.currentParam = ch;
558 this._terminal.prefix += ch;
561 this._terminal.currentParam += ch;
565 case ParserState.IGNORE:
567 if (ch === C0.ESC || ch === C0.BEL) {
568 if (ch === C0.ESC) this._position++;
569 this._state = ParserState.NORMAL;
578 * Set the parser's current parsing state.
580 * @param state The new state.
582 public setState(state: ParserState): void {
587 * Sets the parsier's current prefix. CSI codes can have prefixes of '?', '>'
590 * @param prefix The prefix.
592 public setPrefix(prefix: string): void {
593 this._terminal.prefix = prefix;
597 * Sets the parsier's current prefix. CSI codes can have postfixes of '$',
600 * @param postfix The postfix.
602 public setPostfix(postfix: string): void {
603 this._terminal.postfix = postfix;
607 * Sets the parser's current parameter.
609 * @param param the parameter.
611 public setParam(param: number) {
612 this._terminal.currentParam = param;
616 * Gets the parser's current parameter.
618 public getParam(): number {
619 return this._terminal.currentParam;
623 * Finalizes the parser's current parameter, adding it to the list of
624 * parameters and setting the new current parameter to 0.
626 public finalizeParam(): void {
627 this._terminal.params.push(this._terminal.currentParam);
628 this._terminal.currentParam = 0;
632 * Tell the parser to skip the next character.
634 public skipNextChar(): void {
639 * Tell the parser to repeat parsing the current character (for example if it
640 * needs parsing using a different state.
642 // public repeatChar(): void {