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