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