UNPKG

20.2 kBJavaScriptView Raw
1'use strict';
2
3var states = require('./states');
4
5function fixLinefeed(data) {
6 return data.replace(/([^\r])\n/g, '$1\r\n');
7}
8
9function fixIndent(data) {
10 if (!/(^|\n) /.test(data)) return data;
11
12 // not very efficient, but works and would only become a problem
13 // once we render huge amounts of data
14 return data
15 .split('\n')
16 .map(function (line) {
17 var count = 0;
18 while(line.charAt(0) === ' '){
19 line = line.slice(1);
20 count++;
21 }
22 while(count--) {
23 line = ' ' + line;
24 }
25 return line;
26 })
27 .join('\r\n');
28}
29
30module.exports = function(Terminal) {
31
32 Terminal.prototype.write = function(data) {
33
34 data = fixLinefeed(data);
35 data = fixIndent(data);
36
37 var l = data.length,
38 i = 0,
39 cs, ch;
40
41 this.refreshStart = this.y;
42 this.refreshEnd = this.y;
43
44 if (this.ybase !== this.ydisp) {
45 this.ydisp = this.ybase;
46 this.maxRange();
47 }
48
49 // this.log(JSON.stringify(data.replace(/\x1b/g, '^[')));
50
51 for (; i < l; i++) {
52 ch = data[i];
53 switch (this.state) {
54 case states.normal:
55 switch (ch) {
56 // '\0'
57 // case '\0':
58 // break;
59
60 // '\a'
61 case '\x07':
62 this.bell();
63 break;
64
65 // '\n', '\v', '\f'
66 case '\n':
67 case '\x0b':
68 case '\x0c':
69 if (this.convertEol) {
70 this.x = 0;
71 }
72 this.y++;
73 break;
74
75 // '\r'
76 case '\r':
77 this.x = 0;
78 break;
79
80 // '\b'
81 case '\x08':
82 if (this.x > 0) {
83 this.x--;
84 }
85 break;
86
87 // '\t'
88 case '\t':
89 this.x = this.nextStop();
90 break;
91
92 // shift out
93 case '\x0e':
94 this.setgLevel(1);
95 break;
96
97 // shift in
98 case '\x0f':
99 this.setgLevel(0);
100 break;
101
102 // '\e'
103 case '\x1b':
104 this.state = states.escaped;
105 break;
106
107 default:
108 // ' '
109 if (ch >= ' ') {
110 if (this.charset && this.charset[ch]) {
111 ch = this.charset[ch];
112 }
113 if (this.x >= this.cols) {
114 this.x = 0;
115 this.y++;
116 }
117 this.lines[this.y + this.ybase][this.x] = [this.curAttr, ch];
118 this.x++;
119 this.updateRange(this.y);
120 }
121 break;
122 }
123 break;
124 case states.escaped:
125 switch (ch) {
126 // ESC [ Control Sequence Introducer ( CSI is 0x9b).
127 case '[':
128 this.params = [];
129 this.currentParam = 0;
130 this.state = states.csi;
131 break;
132
133 // ESC ] Operating System Command ( OSC is 0x9d).
134 case ']':
135 this.params = [];
136 this.currentParam = 0;
137 this.state = states.osc;
138 break;
139
140 // ESC P Device Control String ( DCS is 0x90).
141 case 'P':
142 this.params = [];
143 this.currentParam = 0;
144 this.state = states.dcs;
145 break;
146
147 // ESC _ Application Program Command ( APC is 0x9f).
148 case '_':
149 this.stateType = 'apc';
150 this.state = states.ignore;
151 break;
152
153 // ESC ^ Privacy Message ( PM is 0x9e).
154 case '^':
155 this.stateType = 'pm';
156 this.state = states.ignore;
157 break;
158
159 // ESC c Full Reset (RIS).
160 case 'c':
161 this.reset();
162 break;
163
164 // ESC E Next Line ( NEL is 0x85).
165 // ESC D Index ( IND is 0x84).
166 case 'E':
167 this.x = 0;
168 break;
169 case 'D':
170 this.index();
171 break;
172
173 // ESC M Reverse Index ( RI is 0x8d).
174 case 'M':
175 this.reverseIndex();
176 break;
177
178 // ESC % Select default/utf-8 character set.
179 // @ = default, G = utf-8
180 case '%':
181 //this.charset = null;
182 this.setgLevel(0);
183 this.setgCharset(0, Terminal.charsets.US);
184 this.state = states.normal;
185 i++;
186 break;
187
188 // ESC (,),*,+,-,. Designate G0-G2 Character Set.
189 case '(':
190 // <-- this seems to get all the attention
191 case ')':
192 case '*':
193 case '+':
194 case '-':
195 case '.':
196 switch (ch) {
197 case '(':
198 this.gcharset = 0;
199 break;
200 case ')':
201 this.gcharset = 1;
202 break;
203 case '*':
204 this.gcharset = 2;
205 break;
206 case '+':
207 this.gcharset = 3;
208 break;
209 case '-':
210 this.gcharset = 1;
211 break;
212 case '.':
213 this.gcharset = 2;
214 break;
215 }
216 this.state = states.charset;
217 break;
218
219 // Designate G3 Character Set (VT300).
220 // A = ISO Latin-1 Supplemental.
221 // Not implemented.
222 case '/':
223 this.gcharset = 3;
224 this.state = states.charset;
225 i--;
226 break;
227
228 // ESC N
229 // Single Shift Select of G2 Character Set
230 // ( SS2 is 0x8e). This affects next character only.
231 case 'N':
232 break;
233 // ESC O
234 // Single Shift Select of G3 Character Set
235 // ( SS3 is 0x8f). This affects next character only.
236 case 'O':
237 break;
238 // ESC n
239 // Invoke the G2 Character Set as GL (LS2).
240 case 'n':
241 this.setgLevel(2);
242 break;
243 // ESC o
244 // Invoke the G3 Character Set as GL (LS3).
245 case 'o':
246 this.setgLevel(3);
247 break;
248 // ESC |
249 // Invoke the G3 Character Set as GR (LS3R).
250 case '|':
251 this.setgLevel(3);
252 break;
253 // ESC }
254 // Invoke the G2 Character Set as GR (LS2R).
255 case '}':
256 this.setgLevel(2);
257 break;
258 // ESC ~
259 // Invoke the G1 Character Set as GR (LS1R).
260 case '~':
261 this.setgLevel(1);
262 break;
263
264 // ESC 7 Save Cursor (DECSC).
265 case '7':
266 this.saveCursor();
267 this.state = states.normal;
268 break;
269
270 // ESC 8 Restore Cursor (DECRC).
271 case '8':
272 this.restoreCursor();
273 this.state = states.normal;
274 break;
275
276 // ESC # 3 DEC line height/width
277 case '#':
278 this.state = states.normal;
279 i++;
280 break;
281
282 // ESC H Tab Set (HTS is 0x88).
283 case 'H':
284 this.tabSet();
285 break;
286
287 // ESC = Application Keypad (DECPAM).
288 case '=':
289 this.log('Serial port requested application keypad.');
290 this.applicationKeypad = true;
291 this.state = states.normal;
292 break;
293
294 // ESC > Normal Keypad (DECPNM).
295 case '>':
296 this.log('Switching back to normal keypad.');
297 this.applicationKeypad = false;
298 this.state = states.normal;
299 break;
300
301 default:
302 this.state = states.normal;
303 this.error('Unknown ESC control: %s.', ch);
304 break;
305 }
306 break;
307
308 case states.charset:
309 switch (ch) {
310 case '0':
311 // DEC Special Character and Line Drawing Set.
312 cs = Terminal.charsets.SCLD;
313 break;
314 case 'A':
315 // UK
316 cs = Terminal.charsets.UK;
317 break;
318 case 'B':
319 // United States (USASCII).
320 cs = Terminal.charsets.US;
321 break;
322 case '4':
323 // Dutch
324 cs = Terminal.charsets.Dutch;
325 break;
326 case 'C':
327 // Finnish
328 case '5':
329 cs = Terminal.charsets.Finnish;
330 break;
331 case 'R':
332 // French
333 cs = Terminal.charsets.French;
334 break;
335 case 'Q':
336 // FrenchCanadian
337 cs = Terminal.charsets.FrenchCanadian;
338 break;
339 case 'K':
340 // German
341 cs = Terminal.charsets.German;
342 break;
343 case 'Y':
344 // Italian
345 cs = Terminal.charsets.Italian;
346 break;
347 case 'E':
348 // NorwegianDanish
349 case '6':
350 cs = Terminal.charsets.NorwegianDanish;
351 break;
352 case 'Z':
353 // Spanish
354 cs = Terminal.charsets.Spanish;
355 break;
356 case 'H':
357 // Swedish
358 case '7':
359 cs = Terminal.charsets.Swedish;
360 break;
361 case '=':
362 // Swiss
363 cs = Terminal.charsets.Swiss;
364 break;
365 case '/':
366 // ISOLatin (actually /A)
367 cs = Terminal.charsets.ISOLatin;
368 i++;
369 break;
370 default:
371 // Default
372 cs = Terminal.charsets.US;
373 break;
374 }
375 this.setgCharset(this.gcharset, cs);
376 this.gcharset = null;
377 this.state = states.normal;
378 break;
379
380 case states.osc:
381 // OSC Ps ; Pt ST
382 // OSC Ps ; Pt BEL
383 // Set Text Parameters.
384 if (ch === '\x1b' || ch === '\x07') {
385 if (ch === '\x1b') i++;
386
387 this.params.push(this.currentParam);
388
389 switch (this.params[0]) {
390 case 0:
391 case 1:
392 case 2:
393 if (this.params[1]) {
394 this.title = this.params[1];
395 this.handleTitle(this.title);
396 }
397 break;
398 case 3:
399 // set X property
400 break;
401 case 4:
402 case 5:
403 // change dynamic colors
404 break;
405 case 10:
406 case 11:
407 case 12:
408 case 13:
409 case 14:
410 case 15:
411 case 16:
412 case 17:
413 case 18:
414 case 19:
415 // change dynamic ui colors
416 break;
417 case 46:
418 // change log file
419 break;
420 case 50:
421 // dynamic font
422 break;
423 case 51:
424 // emacs shell
425 break;
426 case 52:
427 // manipulate selection data
428 break;
429 case 104:
430 case 105:
431 case 110:
432 case 111:
433 case 112:
434 case 113:
435 case 114:
436 case 115:
437 case 116:
438 case 117:
439 case 118:
440 // reset colors
441 break;
442 }
443
444 this.params = [];
445 this.currentParam = 0;
446 this.state = states.normal;
447 } else {
448 if (!this.params.length) {
449 if (ch >= '0' && ch <= '9') {
450 this.currentParam = this.currentParam * 10 + ch.charCodeAt(0) - 48;
451 } else if (ch === ';') {
452 this.params.push(this.currentParam);
453 this.currentParam = '';
454 }
455 } else {
456 this.currentParam += ch;
457 }
458 }
459 break;
460
461 case states.csi:
462 // '?', '>', '!'
463 if (ch === '?' || ch === '>' || ch === '!') {
464 this.prefix = ch;
465 break;
466 }
467
468 // 0 - 9
469 if (ch >= '0' && ch <= '9') {
470 this.currentParam = this.currentParam * 10 + ch.charCodeAt(0) - 48;
471 break;
472 }
473
474 // '$', '"', ' ', '\''
475 if (ch === '$' || ch === '"' || ch === ' ' || ch === '\'') {
476 this.postfix = ch;
477 break;
478 }
479
480 this.params.push(this.currentParam);
481 this.currentParam = 0;
482
483 // ';'
484 if (ch === ';') break;
485
486 this.state = states.normal;
487
488 switch (ch) {
489 // CSI Ps A
490 // Cursor Up Ps Times (default = 1) (CUU).
491 case 'A':
492 this.cursorUp(this.params);
493 break;
494
495 // CSI Ps B
496 // Cursor Down Ps Times (default = 1) (CUD).
497 case 'B':
498 this.cursorDown(this.params);
499 break;
500
501 // CSI Ps C
502 // Cursor Forward Ps Times (default = 1) (CUF).
503 case 'C':
504 this.cursorForward(this.params);
505 break;
506
507 // CSI Ps D
508 // Cursor Backward Ps Times (default = 1) (CUB).
509 case 'D':
510 this.cursorBackward(this.params);
511 break;
512
513 // CSI Ps ; Ps H
514 // Cursor Position [row;column] (default = [1,1]) (CUP).
515 case 'H':
516 this.cursorPos(this.params);
517 break;
518
519 // CSI Ps J Erase in Display (ED).
520 case 'J':
521 this.eraseInDisplay(this.params);
522 break;
523
524 // CSI Ps K Erase in Line (EL).
525 case 'K':
526 this.eraseInLine(this.params);
527 break;
528
529 // CSI Pm m Character Attributes (SGR).
530 case 'm':
531 this.charAttributes(this.params);
532 break;
533
534 // CSI Ps n Device Status Report (DSR).
535 case 'n':
536 this.deviceStatus(this.params);
537 break;
538
539 /**
540 * Additions
541 */
542
543 // CSI Ps @
544 // Insert Ps (Blank) Character(s) (default = 1) (ICH).
545 case '@':
546 this.insertChars(this.params);
547 break;
548
549 // CSI Ps E
550 // Cursor Next Line Ps Times (default = 1) (CNL).
551 case 'E':
552 this.cursorNextLine(this.params);
553 break;
554
555 // CSI Ps F
556 // Cursor Preceding Line Ps Times (default = 1) (CNL).
557 case 'F':
558 this.cursorPrecedingLine(this.params);
559 break;
560
561 // CSI Ps G
562 // Cursor Character Absolute [column] (default = [row,1]) (CHA).
563 case 'G':
564 this.cursorCharAbsolute(this.params);
565 break;
566
567 // CSI Ps L
568 // Insert Ps Line(s) (default = 1) (IL).
569 case 'L':
570 this.insertLines(this.params);
571 break;
572
573 // CSI Ps M
574 // Delete Ps Line(s) (default = 1) (DL).
575 case 'M':
576 this.deleteLines(this.params);
577 break;
578
579 // CSI Ps P
580 // Delete Ps Character(s) (default = 1) (DCH).
581 case 'P':
582 this.deleteChars(this.params);
583 break;
584
585 // CSI Ps X
586 // Erase Ps Character(s) (default = 1) (ECH).
587 case 'X':
588 this.eraseChars(this.params);
589 break;
590
591 // CSI Pm ` Character Position Absolute
592 // [column] (default = [row,1]) (HPA).
593 case '`':
594 this.charPosAbsolute(this.params);
595 break;
596
597 // 141 61 a * HPR -
598 // Horizontal Position Relative
599 case 'a':
600 this.HPositionRelative(this.params);
601 break;
602
603 // CSI P s c
604 // Send Device Attributes (Primary DA).
605 // CSI > P s c
606 // Send Device Attributes (Secondary DA)
607 case 'c':
608 //- this.sendDeviceAttributes(this.params);
609 break;
610
611 // CSI Pm d
612 // Line Position Absolute [row] (default = [1,column]) (VPA).
613 case 'd':
614 this.linePosAbsolute(this.params);
615 break;
616
617 // 145 65 e * VPR - Vertical Position Relative
618 case 'e':
619 this.VPositionRelative(this.params);
620 break;
621
622 // CSI Ps ; Ps f
623 // Horizontal and Vertical Position [row;column] (default =
624 // [1,1]) (HVP).
625 case 'f':
626 this.HVPosition(this.params);
627 break;
628
629 // CSI Pm h Set Mode (SM).
630 // CSI ? Pm h - mouse escape codes, cursor escape codes
631 case 'h':
632 //- this.setMode(this.params);
633 break;
634
635 // CSI Pm l Reset Mode (RM).
636 // CSI ? Pm l
637 case 'l':
638 //- this.resetMode(this.params);
639 break;
640
641 // CSI Ps ; Ps r
642 // Set Scrolling Region [top;bottom] (default = full size of win-
643 // dow) (DECSTBM).
644 // CSI ? Pm r
645 case 'r':
646 //- this.setScrollRegion(this.params);
647 break;
648
649 // CSI s
650 // Save cursor (ANSI.SYS).
651 case 's':
652 this.saveCursor(this.params);
653 break;
654
655 // CSI u
656 // Restore cursor (ANSI.SYS).
657 case 'u':
658 this.restoreCursor(this.params);
659 break;
660
661 /**
662 * Lesser Used
663 */
664
665 // CSI Ps I
666 // Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).
667 case 'I':
668 this.cursorForwardTab(this.params);
669 break;
670
671 // CSI Ps S Scroll up Ps lines (default = 1) (SU).
672 case 'S':
673 //- this.scrollUp(this.params);
674 break;
675
676 // CSI Ps T Scroll down Ps lines (default = 1) (SD).
677 // CSI Ps ; Ps ; Ps ; Ps ; Ps T
678 // CSI > Ps; Ps T
679 case 'T':
680 if (this.params.length < 2 && !this.prefix) {
681 //- this.scrollDown(this.params);
682 }
683 break;
684
685 // CSI Ps Z
686 // Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).
687 case 'Z':
688 this.cursorBackwardTab(this.params);
689 break;
690
691 // CSI Ps b Repeat the preceding graphic character Ps times (REP).
692 case 'b':
693 this.repeatPrecedingCharacter(this.params);
694 break;
695
696 // CSI Ps g Tab Clear (TBC).
697 case 'g':
698 this.tabClear(this.params);
699 break;
700 case 'p':
701 switch (this.prefix) {
702 case '!':
703 this.softReset(this.params);
704 break;
705 }
706 break;
707
708 default:
709 this.error('Unknown CSI code: %s.', ch);
710 break;
711 }
712
713 this.prefix = '';
714 this.postfix = '';
715 break;
716
717 case states.dcs:
718 if (ch === '\x1b' || ch === '\x07') {
719 if (ch === '\x1b') i++;
720
721 switch (this.prefix) {
722 // User-Defined Keys (DECUDK).
723 case '':
724 break;
725
726 // Request Status String (DECRQSS).
727 // test: echo -e '\eP$q"p\e\\'
728 case '$q':
729 var pt = this.currentParam,
730 valid = false;
731
732 switch (pt) {
733 // DECSCA
734 case '"q':
735 pt = '0"q';
736 break;
737
738 // DECSCL
739 case '"p':
740 pt = '61"p';
741 break;
742
743 // DECSTBM
744 case 'r':
745 pt = '' + (this.scrollTop + 1) + ';' + (this.scrollBottom + 1) + 'r';
746 break;
747
748 // SGR
749 case 'm':
750 pt = '0m';
751 break;
752
753 default:
754 this.error('Unknown DCS Pt: %s.', pt);
755 pt = '';
756 break;
757 }
758
759 //- this.send('\x1bP' + valid + '$r' + pt + '\x1b\\');
760 break;
761
762 // Set Termcap/Terminfo Data (xterm, experimental).
763 case '+p':
764 break;
765
766 default:
767 this.error('Unknown DCS prefix: %s.', this.prefix);
768 break;
769 }
770
771 this.currentParam = 0;
772 this.prefix = '';
773 this.state = states.normal;
774 } else if (!this.currentParam) {
775 if (!this.prefix && ch !== '$' && ch !== '+') {
776 this.currentParam = ch;
777 } else if (this.prefix.length === 2) {
778 this.currentParam = ch;
779 } else {
780 this.prefix += ch;
781 }
782 } else {
783 this.currentParam += ch;
784 }
785 break;
786
787 case states.ignore:
788 // For PM and APC.
789 if (ch === '\x1b' || ch === '\x07') {
790 if (ch === '\x1b') i++;
791 this.stateData = '';
792 this.state = states.normal;
793 } else {
794 if (!this.stateData) this.stateData = '';
795 this.stateData += ch;
796 }
797 break;
798 }
799 }
800
801 this.updateRange(this.y);
802 this.refresh(this.refreshStart, this.refreshEnd);
803 };
804
805 Terminal.prototype.writeln = function(data) {
806 // properly render empty lines
807 if (!data.trim().length) data = '&nbsp;';
808 this.write(data + '\r\n');
809
810 // fix newlines not properly respected otherwise
811 if (data.charAt(data.length - 1) === '\r\n') this.writeln('&nbsp;');
812 };
813};