]> git.proxmox.com Git - mirror_xterm.js.git/blob - src/Parser.ts
Implement CAN (Cancel, ^X)
[mirror_xterm.js.git] / src / Parser.ts
1 /**
2 * @license MIT
3 */
4
5 import { C0 } from './EscapeSequences';
6 import { IInputHandler } from './Interfaces';
7 import { CHARSETS } from './Charsets';
8
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);
20
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)
25 terminal.params = [];
26 terminal.currentParam = 0;
27 parser.setState(ParserState.CSI_PARAM);
28 };
29 escapedStateHandler[']'] = (parser, terminal) => {
30 // ESC ] Operating System Command (OSC is 0x9d)
31 terminal.params = [];
32 terminal.currentParam = 0;
33 parser.setState(ParserState.OSC);
34 };
35 escapedStateHandler['P'] = (parser, terminal) => {
36 // ESC P Device Control String (DCS is 0x90)
37 terminal.params = [];
38 terminal.currentParam = 0;
39 parser.setState(ParserState.DCS);
40 };
41 escapedStateHandler['_'] = (parser, terminal) => {
42 // ESC _ Application Program Command ( APC is 0x9f).
43 parser.setState(ParserState.IGNORE);
44 };
45 escapedStateHandler['^'] = (parser, terminal) => {
46 // ESC ^ Privacy Message ( PM is 0x9e).
47 parser.setState(ParserState.IGNORE);
48 };
49 escapedStateHandler['c'] = (parser, terminal) => {
50 // ESC c Full Reset (RIS).
51 terminal.reset();
52 };
53 escapedStateHandler['E'] = (parser, terminal) => {
54 // ESC E Next Line ( NEL is 0x85).
55 terminal.x = 0;
56 terminal.index();
57 parser.setState(ParserState.NORMAL);
58 };
59 escapedStateHandler['D'] = (parser, terminal) => {
60 // ESC D Index ( IND is 0x84).
61 terminal.index();
62 parser.setState(ParserState.NORMAL);
63 };
64 escapedStateHandler['M'] = (parser, terminal) => {
65 // ESC M Reverse Index ( RI is 0x8d).
66 terminal.reverseIndex();
67 parser.setState(ParserState.NORMAL);
68 };
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, CHARSETS.US);
74 parser.setState(ParserState.NORMAL);
75 parser.skipNextChar();
76 };
77 escapedStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);
78
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);
99
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);
120 }
121 };
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) => {
137 switch (prefix) {
138 case '!': handler.softReset(params); break;
139 }
140 };
141 csiStateHandler['q'] = (handler, params, prefix, postfix) => {
142 if (postfix === ' ') {
143 handler.setCursorStyle(params);
144 }
145 };
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);
150
151 // TODO: Many codes/charsets appear to not be supported
152 // See: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
153 const charsetMap = {
154 '0': CHARSETS.SCLD,
155 'A': CHARSETS.UK, // United Kingdom
156 'B': CHARSETS.US, // United States (USASCII)
157 '4': CHARSETS.Dutch,
158 'C': CHARSETS.Finnish,
159 '5': CHARSETS.Finnish,
160 'f': CHARSETS.French,
161 'Q': CHARSETS.FrenchCanadian,
162 'K': CHARSETS.German,
163 'Y': CHARSETS.Italian,
164 'E': CHARSETS.NorwegianDanish,
165 '6': CHARSETS.NorwegianDanish,
166 'Z': CHARSETS.Spanish,
167 'H': CHARSETS.Swedish,
168 '7': CHARSETS.Swedish,
169 '=': CHARSETS.Swiss,
170 '/': CHARSETS.ISOLatin // ISOLatin is actually /A
171 };
172
173 enum ParserState {
174 NORMAL = 0,
175 ESCAPED = 1,
176 CSI_PARAM = 2,
177 CSI = 3,
178 OSC = 4,
179 CHARSET = 5,
180 DCS = 6,
181 IGNORE = 7
182 }
183
184 /**
185 * The terminal's parser, all input into the terminal goes through the parser
186 * which parses and defers the actual input handling the the IInputHandler
187 * specified in the constructor.
188 */
189 export class Parser {
190 private _state: ParserState;
191 private _position: number;
192
193 // TODO: Remove terminal when handler can do everything
194 constructor(
195 private _inputHandler: IInputHandler,
196 private _terminal: any
197 ) {
198 this._state = ParserState.NORMAL;
199 }
200
201 /**
202 * Parse and handle data.
203 *
204 * @param data The data to parse.
205 */
206 public parse(data: string) {
207 let l = data.length, j, cs, ch, code, low;
208
209 this._position = 0;
210 // apply leftover surrogate high from last write
211 if (this._terminal.surrogate_high) {
212 data = this._terminal.surrogate_high + data;
213 this._terminal.surrogate_high = '';
214 }
215
216 for (; this._position < l; this._position++) {
217 ch = data[this._position];
218
219 // FIXME: higher chars than 0xa0 are not allowed in escape sequences
220 // --> maybe move to default
221 code = data.charCodeAt(this._position);
222 if (0xD800 <= code && code <= 0xDBFF) {
223 // we got a surrogate high
224 // get surrogate low (next 2 bytes)
225 low = data.charCodeAt(this._position + 1);
226 if (isNaN(low)) {
227 // end of data stream, save surrogate high
228 this._terminal.surrogate_high = ch;
229 continue;
230 }
231 code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
232 ch += data.charAt(this._position + 1);
233 }
234 // surrogate low - already handled above
235 if (0xDC00 <= code && code <= 0xDFFF)
236 continue;
237
238 switch (this._state) {
239 case ParserState.NORMAL:
240 if (ch in normalStateHandler) {
241 normalStateHandler[ch](this, this._inputHandler);
242 } else {
243 this._inputHandler.addChar(ch, code);
244 }
245 break;
246 case ParserState.ESCAPED:
247 if (ch in escapedStateHandler) {
248 escapedStateHandler[ch](this, this._terminal);
249 // Skip switch as it was just handled
250 break;
251 }
252 switch (ch) {
253
254 // ESC (,),*,+,-,. Designate G0-G2 Character Set.
255 case '(': // <-- this seems to get all the attention
256 case ')':
257 case '*':
258 case '+':
259 case '-':
260 case '.':
261 switch (ch) {
262 case '(':
263 this._terminal.gcharset = 0;
264 break;
265 case ')':
266 this._terminal.gcharset = 1;
267 break;
268 case '*':
269 this._terminal.gcharset = 2;
270 break;
271 case '+':
272 this._terminal.gcharset = 3;
273 break;
274 case '-':
275 this._terminal.gcharset = 1;
276 break;
277 case '.':
278 this._terminal.gcharset = 2;
279 break;
280 }
281 this._state = ParserState.CHARSET;
282 break;
283
284 // Designate G3 Character Set (VT300).
285 // A = ISO Latin-1 Supplemental.
286 // Not implemented.
287 case '/':
288 this._terminal.gcharset = 3;
289 this._state = ParserState.CHARSET;
290 this._position--;
291 break;
292
293 // ESC N
294 // Single Shift Select of G2 Character Set
295 // ( SS2 is 0x8e). This affects next character only.
296 case 'N':
297 break;
298 // ESC O
299 // Single Shift Select of G3 Character Set
300 // ( SS3 is 0x8f). This affects next character only.
301 case 'O':
302 break;
303 // ESC n
304 // Invoke the G2 Character Set as GL (LS2).
305 case 'n':
306 this._terminal.setgLevel(2);
307 break;
308 // ESC o
309 // Invoke the G3 Character Set as GL (LS3).
310 case 'o':
311 this._terminal.setgLevel(3);
312 break;
313 // ESC |
314 // Invoke the G3 Character Set as GR (LS3R).
315 case '|':
316 this._terminal.setgLevel(3);
317 break;
318 // ESC }
319 // Invoke the G2 Character Set as GR (LS2R).
320 case '}':
321 this._terminal.setgLevel(2);
322 break;
323 // ESC ~
324 // Invoke the G1 Character Set as GR (LS1R).
325 case '~':
326 this._terminal.setgLevel(1);
327 break;
328
329 // ESC 7 Save Cursor (DECSC).
330 case '7':
331 this._inputHandler.saveCursor();
332 this._state = ParserState.NORMAL;
333 break;
334
335 // ESC 8 Restore Cursor (DECRC).
336 case '8':
337 this._inputHandler.restoreCursor();
338 this._state = ParserState.NORMAL;
339 break;
340
341 // ESC # 3 DEC line height/width
342 case '#':
343 this._state = ParserState.NORMAL;
344 this._position++;
345 break;
346
347 // ESC H Tab Set (HTS is 0x88).
348 case 'H':
349 this._terminal.tabSet();
350 this._state = ParserState.NORMAL;
351 break;
352
353 // ESC = Application Keypad (DECKPAM).
354 case '=':
355 this._terminal.log('Serial port requested application keypad.');
356 this._terminal.applicationKeypad = true;
357 this._terminal.viewport.syncScrollArea();
358 this._state = ParserState.NORMAL;
359 break;
360
361 // ESC > Normal Keypad (DECKPNM).
362 case '>':
363 this._terminal.log('Switching back to normal keypad.');
364 this._terminal.applicationKeypad = false;
365 this._terminal.viewport.syncScrollArea();
366 this._state = ParserState.NORMAL;
367 break;
368
369 default:
370 this._state = ParserState.NORMAL;
371 this._terminal.error('Unknown ESC control: %s.', ch);
372 break;
373 }
374 break;
375
376 case ParserState.CHARSET:
377 if (ch in charsetMap) {
378 cs = charsetMap[ch];
379 if (ch === '/') { // ISOLatin is actually /A
380 this.skipNextChar();
381 }
382 } else {
383 cs = CHARSETS.US; // Default
384 }
385 this._terminal.setgCharset(this._terminal.gcharset, cs);
386 this._terminal.gcharset = null;
387 this._state = ParserState.NORMAL;
388 break;
389
390 case ParserState.OSC:
391 // OSC Ps ; Pt ST
392 // OSC Ps ; Pt BEL
393 // Set Text Parameters.
394 if (ch === C0.ESC || ch === C0.BEL) {
395 if (ch === C0.ESC) this._position++;
396
397 this._terminal.params.push(this._terminal.currentParam);
398
399 switch (this._terminal.params[0]) {
400 case 0:
401 case 1:
402 case 2:
403 if (this._terminal.params[1]) {
404 this._terminal.title = this._terminal.params[1];
405 this._terminal.handleTitle(this._terminal.title);
406 }
407 break;
408 case 3:
409 // set X property
410 break;
411 case 4:
412 case 5:
413 // change dynamic colors
414 break;
415 case 10:
416 case 11:
417 case 12:
418 case 13:
419 case 14:
420 case 15:
421 case 16:
422 case 17:
423 case 18:
424 case 19:
425 // change dynamic ui colors
426 break;
427 case 46:
428 // change log file
429 break;
430 case 50:
431 // dynamic font
432 break;
433 case 51:
434 // emacs shell
435 break;
436 case 52:
437 // manipulate selection data
438 break;
439 case 104:
440 case 105:
441 case 110:
442 case 111:
443 case 112:
444 case 113:
445 case 114:
446 case 115:
447 case 116:
448 case 117:
449 case 118:
450 // reset colors
451 break;
452 }
453
454 this._terminal.params = [];
455 this._terminal.currentParam = 0;
456 this._state = ParserState.NORMAL;
457 } else {
458 if (!this._terminal.params.length) {
459 if (ch >= '0' && ch <= '9') {
460 this._terminal.currentParam =
461 this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48;
462 } else if (ch === ';') {
463 this._terminal.params.push(this._terminal.currentParam);
464 this._terminal.currentParam = '';
465 }
466 } else {
467 this._terminal.currentParam += ch;
468 }
469 }
470 break;
471
472 case ParserState.CSI_PARAM:
473 if (ch in csiParamStateHandler) {
474 csiParamStateHandler[ch](this);
475 break;
476 }
477 this.finalizeParam();
478 // Fall through the CSI as this character should be the CSI code.
479 this._state = ParserState.CSI;
480
481 case ParserState.CSI:
482 if (ch in csiStateHandler) {
483 csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this);
484 } else {
485 this._terminal.error('Unknown CSI code: %s.', ch);
486 }
487
488 this._state = ParserState.NORMAL;
489 this._terminal.prefix = '';
490 this._terminal.postfix = '';
491 break;
492
493 case ParserState.DCS:
494 if (ch === C0.ESC || ch === C0.BEL) {
495 if (ch === C0.ESC) this._position++;
496
497 switch (this._terminal.prefix) {
498 // User-Defined Keys (DECUDK).
499 case '':
500 break;
501
502 // Request Status String (DECRQSS).
503 // test: echo -e '\eP$q"p\e\\'
504 case '$q':
505 let pt = this._terminal.currentParam
506 , valid = false;
507
508 switch (pt) {
509 // DECSCA
510 case '"q':
511 pt = '0"q';
512 break;
513
514 // DECSCL
515 case '"p':
516 pt = '61"p';
517 break;
518
519 // DECSTBM
520 case 'r':
521 pt = ''
522 + (this._terminal.scrollTop + 1)
523 + ';'
524 + (this._terminal.scrollBottom + 1)
525 + 'r';
526 break;
527
528 // SGR
529 case 'm':
530 pt = '0m';
531 break;
532
533 default:
534 this._terminal.error('Unknown DCS Pt: %s.', pt);
535 pt = '';
536 break;
537 }
538
539 this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\');
540 break;
541
542 // Set Termcap/Terminfo Data (xterm, experimental).
543 case '+p':
544 break;
545
546 // Request Termcap/Terminfo String (xterm, experimental)
547 // Regular xterm does not even respond to this sequence.
548 // This can cause a small glitch in vim.
549 // test: echo -ne '\eP+q6b64\e\\'
550 case '+q':
551 // TODO: Don't declare pt twice
552 /*let*/ pt = this._terminal.currentParam
553 , valid = false;
554
555 this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\');
556 break;
557
558 default:
559 this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix);
560 break;
561 }
562
563 this._terminal.currentParam = 0;
564 this._terminal.prefix = '';
565 this._state = ParserState.NORMAL;
566 } else if (!this._terminal.currentParam) {
567 if (!this._terminal.prefix && ch !== '$' && ch !== '+') {
568 this._terminal.currentParam = ch;
569 } else if (this._terminal.prefix.length === 2) {
570 this._terminal.currentParam = ch;
571 } else {
572 this._terminal.prefix += ch;
573 }
574 } else {
575 this._terminal.currentParam += ch;
576 }
577 break;
578
579 case ParserState.IGNORE:
580 // For PM and APC.
581 if (ch === C0.ESC || ch === C0.BEL) {
582 if (ch === C0.ESC) this._position++;
583 this._state = ParserState.NORMAL;
584 }
585 break;
586 }
587 }
588 }
589
590 /**
591 * Set the parser's current parsing state.
592 *
593 * @param state The new state.
594 */
595 public setState(state: ParserState): void {
596 this._state = state;
597 }
598
599 /**
600 * Sets the parsier's current prefix. CSI codes can have prefixes of '?', '>'
601 * or '!'.
602 *
603 * @param prefix The prefix.
604 */
605 public setPrefix(prefix: string): void {
606 this._terminal.prefix = prefix;
607 }
608
609 /**
610 * Sets the parsier's current prefix. CSI codes can have postfixes of '$',
611 * '"', ' ', '\''.
612 *
613 * @param postfix The postfix.
614 */
615 public setPostfix(postfix: string): void {
616 this._terminal.postfix = postfix;
617 }
618
619 /**
620 * Sets the parser's current parameter.
621 *
622 * @param param the parameter.
623 */
624 public setParam(param: number) {
625 this._terminal.currentParam = param;
626 }
627
628 /**
629 * Gets the parser's current parameter.
630 */
631 public getParam(): number {
632 return this._terminal.currentParam;
633 }
634
635 /**
636 * Finalizes the parser's current parameter, adding it to the list of
637 * parameters and setting the new current parameter to 0.
638 */
639 public finalizeParam(): void {
640 this._terminal.params.push(this._terminal.currentParam);
641 this._terminal.currentParam = 0;
642 }
643
644 /**
645 * Tell the parser to skip the next character.
646 */
647 public skipNextChar(): void {
648 this._position++;
649 }
650
651 /**
652 * Tell the parser to repeat parsing the current character (for example if it
653 * needs parsing using a different state.
654 */
655 // public repeatChar(): void {
656 // this._position--;
657 // }
658 }