]> git.proxmox.com Git - mirror_xterm.js.git/blob - src/xterm.js
Merge pull request #257 from Tyriar/improve_copyright_line
[mirror_xterm.js.git] / src / xterm.js
1 /**
2 * xterm.js: xterm, in the browser
3 * Copyright (c) 2014, SourceLair Limited <www.sourcelair.com> (MIT License)
4 * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
5 * https://github.com/chjj/term.js
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 *
25 * Originally forked from (with the author's permission):
26 * Fabrice Bellard's javascript vt100 for jslinux:
27 * http://bellard.org/jslinux/
28 * Copyright (c) 2011 Fabrice Bellard
29 * The original design remains. The terminal itself
30 * has been extended to include xterm CSI codes, among
31 * other features.
32 */
33
34 import { EventEmitter } from './EventEmitter.js';
35
36 /**
37 * Terminal Emulation References:
38 * http://vt100.net/
39 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt
40 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
41 * http://invisible-island.net/vttest/
42 * http://www.inwap.com/pdp10/ansicode.txt
43 * http://linux.die.net/man/4/console_codes
44 * http://linux.die.net/man/7/urxvt
45 */
46
47 // Let it work inside Node.js for automated testing purposes.
48 var document = (typeof window != 'undefined') ? window.document : null;
49
50 /**
51 * Encapsulates the logic for handling compositionstart, compositionupdate and compositionend
52 * events, displaying the in-progress composition to the UI and forwarding the final composition
53 * to the handler.
54 * @param {HTMLTextAreaElement} textarea The textarea that xterm uses for input.
55 * @param {HTMLElement} compositionView The element to display the in-progress composition in.
56 * @param {Terminal} terminal The Terminal to forward the finished composition to.
57 */
58 function CompositionHelper(textarea, compositionView, terminal) {
59 this.textarea = textarea;
60 this.compositionView = compositionView;
61 this.terminal = terminal;
62
63 // Whether input composition is currently happening, eg. via a mobile keyboard, speech input
64 // or IME. This variable determines whether the compositionText should be displayed on the UI.
65 this.isComposing = false;
66
67 // The input currently being composed, eg. via a mobile keyboard, speech input or IME.
68 this.compositionText = null;
69
70 // The position within the input textarea's value of the current composition.
71 this.compositionPosition = { start: null, end: null };
72
73 // Whether a composition is in the process of being sent, setting this to false will cancel
74 // any in-progress composition.
75 this.isSendingComposition = false;
76 }
77
78 /**
79 * Handles the compositionstart event, activating the composition view.
80 */
81 CompositionHelper.prototype.compositionstart = function() {
82 this.isComposing = true;
83 this.compositionPosition.start = this.textarea.value.length;
84 this.compositionView.textContent = '';
85 this.compositionView.classList.add('active');
86 };
87
88 /**
89 * Handles the compositionupdate event, updating the composition view.
90 * @param {CompositionEvent} ev The event.
91 */
92 CompositionHelper.prototype.compositionupdate = function(ev) {
93 this.compositionView.textContent = ev.data;
94 this.updateCompositionElements();
95 var self = this;
96 setTimeout(function() {
97 self.compositionPosition.end = self.textarea.value.length;
98 }, 0);
99 };
100
101 /**
102 * Handles the compositionend event, hiding the composition view and sending the composition to
103 * the handler.
104 */
105 CompositionHelper.prototype.compositionend = function() {
106 this.finalizeComposition(true);
107 };
108
109 /**
110 * Handles the keydown event, routing any necessary events to the CompositionHelper functions.
111 * @return Whether the Terminal should continue processing the keydown event.
112 */
113 CompositionHelper.prototype.keydown = function(ev) {
114 if (this.isComposing || this.isSendingComposition) {
115 if (ev.keyCode === 229) {
116 // Continue composing if the keyCode is the "composition character"
117 return false;
118 } else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
119 // Continue composing if the keyCode is a modifier key
120 return false;
121 } else {
122 // Finish composition immediately. This is mainly here for the case where enter is
123 // pressed and the handler needs to be triggered before the command is executed.
124 this.finalizeComposition(false);
125 }
126 }
127
128 if (ev.keyCode === 229) {
129 // If the "composition character" is used but gets to this point it means a non-composition
130 // character (eg. numbers and punctuation) was pressed when the IME was active.
131 this.handleAnyTextareaChanges();
132 return false;
133 }
134
135 return true;
136 };
137
138 /**
139 * Finalizes the composition, resuming regular input actions. This is called when a composition
140 * is ending.
141 * @param {boolean} waitForPropogation Whether to wait for events to propogate before sending
142 * the input. This should be false if a non-composition keystroke is entered before the
143 * compositionend event is triggered, such as enter, so that the composition is send before
144 * the command is executed.
145 */
146 CompositionHelper.prototype.finalizeComposition = function(waitForPropogation) {
147 this.compositionView.classList.remove('active');
148 this.isComposing = false;
149 this.clearTextareaPosition();
150
151 if (!waitForPropogation) {
152 // Cancel any delayed composition send requests and send the input immediately.
153 this.isSendingComposition = false;
154 var input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);
155 this.terminal.handler(input);
156 } else {
157 // Make a deep copy of the composition position here as a new compositionstart event may
158 // fire before the setTimeout executes.
159 var currentCompositionPosition = {
160 start: this.compositionPosition.start,
161 end: this.compositionPosition.end,
162 }
163
164 // Since composition* events happen before the changes take place in the textarea on most
165 // browsers, use a setTimeout with 0ms time to allow the native compositionend event to
166 // complete. This ensures the correct character is retrieved, this solution was used
167 // because:
168 // - The compositionend event's data property is unreliable, at least on Chromium
169 // - The last compositionupdate event's data property does not always accurately describe
170 // the character, a counter example being Korean where an ending consonsant can move to
171 // the following character if the following input is a vowel.
172 var self = this;
173 this.isSendingComposition = true;
174 setTimeout(function () {
175 // Ensure that the input has not already been sent
176 if (self.isSendingComposition) {
177 self.isSendingComposition = false;
178 var input;
179 if (self.isComposing) {
180 // Use the end position to get the string if a new composition has started.
181 input = self.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);
182 } else {
183 // Don't use the end position here in order to pick up any characters after the
184 // composition has finished, for example when typing a non-composition character
185 // (eg. 2) after a composition character.
186 input = self.textarea.value.substring(currentCompositionPosition.start);
187 }
188 self.terminal.handler(input);
189 }
190 }, 0);
191 }
192 };
193
194 /**
195 * Apply any changes made to the textarea after the current event chain is allowed to complete.
196 * This should be called when not currently composing but a keydown event with the "composition
197 * character" (229) is triggered, in order to allow non-composition text to be entered when an
198 * IME is active.
199 */
200 CompositionHelper.prototype.handleAnyTextareaChanges = function() {
201 var oldValue = this.textarea.value;
202 var self = this;
203 setTimeout(function() {
204 // Ignore if a composition has started since the timeout
205 if (!self.isComposing) {
206 var newValue = self.textarea.value;
207 var diff = newValue.replace(oldValue, '');
208 if (diff.length > 0) {
209 self.terminal.handler(diff);
210 }
211 }
212 }, 0);
213 };
214
215 /**
216 * Positions the composition view on top of the cursor and the textarea just below it (so the
217 * IME helper dialog is positioned correctly).
218 */
219 CompositionHelper.prototype.updateCompositionElements = function(dontRecurse) {
220 if (!this.isComposing) {
221 return;
222 }
223 var cursor = this.terminal.element.querySelector('.terminal-cursor');
224 if (cursor) {
225 this.compositionView.style.left = cursor.offsetLeft + 'px';
226 this.compositionView.style.top = cursor.offsetTop + 'px';
227 var compositionViewBounds = this.compositionView.getBoundingClientRect();
228 this.textarea.style.left = cursor.offsetLeft + compositionViewBounds.width + 'px';
229 this.textarea.style.top = (cursor.offsetTop + cursor.offsetHeight) + 'px';
230 }
231 if (!dontRecurse) {
232 setTimeout(this.updateCompositionElements.bind(this, true), 0);
233 }
234 };
235
236 /**
237 * Clears the textarea's position so that the cursor does not blink on IE.
238 * @private
239 */
240 CompositionHelper.prototype.clearTextareaPosition = function() {
241 this.textarea.style.left = '';
242 this.textarea.style.top = '';
243 };
244
245 /**
246 * Represents the viewport of a terminal, the visible area within the larger buffer of output.
247 * Logic for the virtual scroll bar is included in this object.
248 * @param {Terminal} terminal The Terminal object.
249 * @param {HTMLElement} viewportElement The DOM element acting as the viewport
250 * @param {HTMLElement} charMeasureElement A DOM element used to measure the character size of
251 * the terminal.
252 */
253 function Viewport(terminal, viewportElement, scrollArea, charMeasureElement) {
254 this.terminal = terminal;
255 this.viewportElement = viewportElement;
256 this.scrollArea = scrollArea;
257 this.charMeasureElement = charMeasureElement;
258 this.currentRowHeight = 0;
259 this.lastRecordedBufferLength = 0;
260 this.lastRecordedViewportHeight = 0;
261
262 this.terminal.on('scroll', this.syncScrollArea.bind(this));
263 this.terminal.on('resize', this.syncScrollArea.bind(this));
264 this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));
265
266 this.syncScrollArea();
267 }
268
269 /**
270 * Refreshes row height, setting line-height, viewport height and scroll area height if
271 * necessary.
272 * @param {number|undefined} charSize A character size measurement bounding rect object, if it
273 * doesn't exist it will be created.
274 */
275 Viewport.prototype.refresh = function(charSize) {
276 var size = charSize || this.charMeasureElement.getBoundingClientRect();
277 if (size.height > 0) {
278 var rowHeightChanged = size.height !== this.currentRowHeight;
279 if (rowHeightChanged) {
280 this.currentRowHeight = size.height;
281 this.viewportElement.style.lineHeight = size.height + 'px';
282 this.terminal.rowContainer.style.lineHeight = size.height + 'px';
283 }
284 var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;
285 if (rowHeightChanged || viewportHeightChanged) {
286 this.lastRecordedViewportHeight = this.terminal.rows;
287 this.viewportElement.style.height = size.height * this.terminal.rows + 'px';
288 }
289 this.scrollArea.style.height = (size.height * this.lastRecordedBufferLength) + 'px';
290 }
291 };
292
293 /**
294 * Updates dimensions and synchronizes the scroll area if necessary.
295 */
296 Viewport.prototype.syncScrollArea = function() {
297 if (this.isApplicationMode) {
298 // Fix scroll bar in application mode
299 this.lastRecordedBufferLength = this.terminal.rows;
300 this.refresh();
301 return;
302 }
303
304 if (this.lastRecordedBufferLength !== this.terminal.lines.length) {
305 // If buffer height changed
306 this.lastRecordedBufferLength = this.terminal.lines.length;
307 this.refresh();
308 } else if (this.lastRecordedViewportHeight !== this.terminal.rows) {
309 // If viewport height changed
310 this.refresh();
311 } else {
312 // If size has changed, refresh viewport
313 var size = this.charMeasureElement.getBoundingClientRect();
314 if (size.height !== this.currentRowHeight) {
315 this.refresh(size);
316 }
317 }
318
319 // Sync scrollTop
320 var scrollTop = this.terminal.ydisp * this.currentRowHeight;
321 if (this.viewportElement.scrollTop !== scrollTop) {
322 this.viewportElement.scrollTop = scrollTop;
323 }
324 };
325
326 /**
327 * Sets the application mode of the viewport.
328 * @param {boolean} isApplicationMode Sets whether the terminal is in application mode. true
329 * for application mode (DECKPAM) and false for normal mode (DECKPNM).
330 */
331 Viewport.prototype.setApplicationMode = function(isApplicationMode) {
332 this.isApplicationMode = isApplicationMode;
333 this.syncScrollArea();
334 };
335
336 /**
337 * Handles scroll events on the viewport, calculating the new viewport and requesting the
338 * terminal to scroll to it.
339 * @param {Event} ev The scroll event.
340 */
341 Viewport.prototype.onScroll = function(ev) {
342 if (this.isApplicationMode) {
343 // Scrolling via the scroll bar is disabled during application mode
344 return;
345 }
346 var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
347 var diff = newRow - this.terminal.ydisp;
348 this.terminal.scrollDisp(diff, true);
349 };
350
351 /**
352 * Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual
353 * scrolling to `onScroll`, this event needs to be attached manually by the consumer of
354 * `Viewport`.
355 * @param {WheelEvent} ev The mouse wheel event.
356 */
357 Viewport.prototype.onWheel = function(ev) {
358 if (ev.deltaY === 0) {
359 // Do nothing if it's not a vertical scroll event
360 return;
361 }
362 // Fallback to WheelEvent.DOM_DELTA_PIXEL
363 var multiplier = 1;
364 if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
365 multiplier = this.currentRowHeight;
366 } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
367 multiplier = this.currentRowHeight * this.terminal.rows;
368 }
369 this.viewportElement.scrollTop += ev.deltaY * multiplier;
370 // Prevent the page from scrolling when the terminal scrolls
371 ev.preventDefault();
372 };
373
374 /**
375 * States
376 */
377 var normal = 0, escaped = 1, csi = 2, osc = 3, charset = 4, dcs = 5, ignore = 6;
378
379 /**
380 * Terminal
381 */
382
383 /**
384 * Creates a new `Terminal` object.
385 *
386 * @param {object} options An object containing a set of options, the available options are:
387 * - cursorBlink (boolean): Whether the terminal cursor blinks
388 *
389 * @public
390 * @class Xterm Xterm
391 * @alias module:xterm/src/xterm
392 */
393 function Terminal(options) {
394 var self = this;
395
396 if (!(this instanceof Terminal)) {
397 return new Terminal(arguments[0], arguments[1], arguments[2]);
398 }
399
400 self.cancel = Terminal.cancel;
401
402 EventEmitter.call(this);
403
404 if (typeof options === 'number') {
405 options = {
406 cols: arguments[0],
407 rows: arguments[1],
408 handler: arguments[2]
409 };
410 }
411
412 options = options || {};
413
414
415 Object.keys(Terminal.defaults).forEach(function(key) {
416 if (options[key] == null) {
417 options[key] = Terminal.options[key];
418
419 if (Terminal[key] !== Terminal.defaults[key]) {
420 options[key] = Terminal[key];
421 }
422 }
423 self[key] = options[key];
424 });
425
426 if (options.colors.length === 8) {
427 options.colors = options.colors.concat(Terminal._colors.slice(8));
428 } else if (options.colors.length === 16) {
429 options.colors = options.colors.concat(Terminal._colors.slice(16));
430 } else if (options.colors.length === 10) {
431 options.colors = options.colors.slice(0, -2).concat(
432 Terminal._colors.slice(8, -2), options.colors.slice(-2));
433 } else if (options.colors.length === 18) {
434 options.colors = options.colors.concat(
435 Terminal._colors.slice(16, -2), options.colors.slice(-2));
436 }
437 this.colors = options.colors;
438
439 this.options = options;
440
441 // this.context = options.context || window;
442 // this.document = options.document || document;
443 this.parent = options.body || options.parent
444 || (document ? document.getElementsByTagName('body')[0] : null);
445
446 this.cols = options.cols || options.geometry[0];
447 this.rows = options.rows || options.geometry[1];
448
449 if (options.handler) {
450 this.on('data', options.handler);
451 }
452
453 /**
454 * The scroll position of the y cursor, ie. ybase + y = the y position within the entire
455 * buffer
456 */
457 this.ybase = 0;
458
459 /**
460 * The scroll position of the viewport
461 */
462 this.ydisp = 0;
463
464 /**
465 * The cursor's x position after ybase
466 */
467 this.x = 0;
468
469 /**
470 * The cursor's y position after ybase
471 */
472 this.y = 0;
473
474 /**
475 * Used to debounce the refresh function
476 */
477 this.isRefreshing = false;
478
479 /**
480 * Whether there is a full terminal refresh queued
481 */
482
483 this.cursorState = 0;
484 this.cursorHidden = false;
485 this.convertEol;
486 this.state = 0;
487 this.queue = '';
488 this.scrollTop = 0;
489 this.scrollBottom = this.rows - 1;
490 this.customKeydownHandler = null;
491
492 // modes
493 this.applicationKeypad = false;
494 this.applicationCursor = false;
495 this.originMode = false;
496 this.insertMode = false;
497 this.wraparoundMode = true; // defaults: xterm - true, vt100 - false
498 this.normal = null;
499
500 // charset
501 this.charset = null;
502 this.gcharset = null;
503 this.glevel = 0;
504 this.charsets = [null];
505
506 // mouse properties
507 this.decLocator;
508 this.x10Mouse;
509 this.vt200Mouse;
510 this.vt300Mouse;
511 this.normalMouse;
512 this.mouseEvents;
513 this.sendFocus;
514 this.utfMouse;
515 this.sgrMouse;
516 this.urxvtMouse;
517
518 // misc
519 this.element;
520 this.children;
521 this.refreshStart;
522 this.refreshEnd;
523 this.savedX;
524 this.savedY;
525 this.savedCols;
526
527 // stream
528 this.readable = true;
529 this.writable = true;
530
531 this.defAttr = (0 << 18) | (257 << 9) | (256 << 0);
532 this.curAttr = this.defAttr;
533
534 this.params = [];
535 this.currentParam = 0;
536 this.prefix = '';
537 this.postfix = '';
538
539 // leftover surrogate high from previous write invocation
540 this.surrogate_high = '';
541
542 /**
543 * An array of all lines in the entire buffer, including the prompt. The lines are array of
544 * characters which are 2-length arrays where [0] is an attribute and [1] is the character.
545 */
546 this.lines = [];
547 var i = this.rows;
548 while (i--) {
549 this.lines.push(this.blankLine());
550 }
551
552 this.tabs;
553 this.setupStops();
554 }
555
556 inherits(Terminal, EventEmitter);
557
558 /**
559 * back_color_erase feature for xterm.
560 */
561 Terminal.prototype.eraseAttr = function() {
562 // if (this.is('screen')) return this.defAttr;
563 return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff);
564 };
565
566 /**
567 * Colors
568 */
569
570 // Colors 0-15
571 Terminal.tangoColors = [
572 // dark:
573 '#2e3436',
574 '#cc0000',
575 '#4e9a06',
576 '#c4a000',
577 '#3465a4',
578 '#75507b',
579 '#06989a',
580 '#d3d7cf',
581 // bright:
582 '#555753',
583 '#ef2929',
584 '#8ae234',
585 '#fce94f',
586 '#729fcf',
587 '#ad7fa8',
588 '#34e2e2',
589 '#eeeeec'
590 ];
591
592 // Colors 0-15 + 16-255
593 // Much thanks to TooTallNate for writing this.
594 Terminal.colors = (function() {
595 var colors = Terminal.tangoColors.slice()
596 , r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
597 , i;
598
599 // 16-231
600 i = 0;
601 for (; i < 216; i++) {
602 out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]);
603 }
604
605 // 232-255 (grey)
606 i = 0;
607 for (; i < 24; i++) {
608 r = 8 + i * 10;
609 out(r, r, r);
610 }
611
612 function out(r, g, b) {
613 colors.push('#' + hex(r) + hex(g) + hex(b));
614 }
615
616 function hex(c) {
617 c = c.toString(16);
618 return c.length < 2 ? '0' + c : c;
619 }
620
621 return colors;
622 })();
623
624 Terminal._colors = Terminal.colors.slice();
625
626 Terminal.vcolors = (function() {
627 var out = []
628 , colors = Terminal.colors
629 , i = 0
630 , color;
631
632 for (; i < 256; i++) {
633 color = parseInt(colors[i].substring(1), 16);
634 out.push([
635 (color >> 16) & 0xff,
636 (color >> 8) & 0xff,
637 color & 0xff
638 ]);
639 }
640
641 return out;
642 })();
643
644 /**
645 * Options
646 */
647
648 Terminal.defaults = {
649 colors: Terminal.colors,
650 theme: 'default',
651 convertEol: false,
652 termName: 'xterm',
653 geometry: [80, 24],
654 cursorBlink: false,
655 visualBell: false,
656 popOnBell: false,
657 scrollback: 1000,
658 screenKeys: false,
659 debug: false,
660 cancelEvents: false
661 // programFeatures: false,
662 // focusKeys: false,
663 };
664
665 Terminal.options = {};
666
667 Terminal.focus = null;
668
669 each(keys(Terminal.defaults), function(key) {
670 Terminal[key] = Terminal.defaults[key];
671 Terminal.options[key] = Terminal.defaults[key];
672 });
673
674 /**
675 * Focus the terminal. Delegates focus handling to the terminal's DOM element.
676 */
677 Terminal.prototype.focus = function() {
678 return this.textarea.focus();
679 };
680
681 /**
682 * Binds the desired focus behavior on a given terminal object.
683 *
684 * @static
685 */
686 Terminal.bindFocus = function (term) {
687 on(term.textarea, 'focus', function (ev) {
688 if (term.sendFocus) {
689 term.send('\x1b[I');
690 }
691 term.element.classList.add('focus');
692 term.showCursor();
693 Terminal.focus = term;
694 term.emit('focus', {terminal: term});
695 });
696 };
697
698 /**
699 * Blur the terminal. Delegates blur handling to the terminal's DOM element.
700 */
701 Terminal.prototype.blur = function() {
702 return this.textarea.blur();
703 };
704
705 /**
706 * Binds the desired blur behavior on a given terminal object.
707 *
708 * @static
709 */
710 Terminal.bindBlur = function (term) {
711 on(term.textarea, 'blur', function (ev) {
712 term.refresh(term.y, term.y);
713 if (term.sendFocus) {
714 term.send('\x1b[O');
715 }
716 term.element.classList.remove('focus');
717 Terminal.focus = null;
718 term.emit('blur', {terminal: term});
719 });
720 };
721
722 /**
723 * Initialize default behavior
724 */
725 Terminal.prototype.initGlobal = function() {
726 Terminal.bindPaste(this);
727 Terminal.bindKeys(this);
728 Terminal.bindCopy(this);
729 Terminal.bindFocus(this);
730 Terminal.bindBlur(this);
731 };
732
733 /**
734 * Bind to paste event and allow both keyboard and right-click pasting, without having the
735 * contentEditable value set to true.
736 */
737 Terminal.bindPaste = function(term) {
738 on([term.textarea, term.element], 'paste', function(ev) {
739 ev.stopPropagation();
740 if (ev.clipboardData) {
741 var text = ev.clipboardData.getData('text/plain');
742 term.handler(text);
743 term.textarea.value = '';
744 return term.cancel(ev);
745 }
746 });
747 };
748
749 /**
750 * Prepares text copied from terminal selection, to be saved in the clipboard by:
751 * 1. stripping all trailing white spaces
752 * 2. converting all non-breaking spaces to regular spaces
753 * @param {string} text The copied text that needs processing for storing in clipboard
754 * @returns {string}
755 * @static
756 */
757 Terminal.prepareCopiedTextForClipboard = function (text) {
758 var space = String.fromCharCode(32),
759 nonBreakingSpace = String.fromCharCode(160),
760 allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'),
761 processedText = text.split('\n').map(function (line) {
762 /**
763 * Strip all trailing white spaces and convert all non-breaking spaces to regular
764 * spaces.
765 */
766 var processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space);
767
768 return processedLine;
769 }).join('\n');
770
771 return processedText;
772 };
773
774 /**
775 * Apply key handling to the terminal
776 */
777 Terminal.bindKeys = function(term) {
778 on(term.element, 'keydown', function(ev) {
779 if (document.activeElement != this) {
780 return;
781 }
782 term.keyDown(ev);
783 }, true);
784
785 on(term.element, 'keypress', function(ev) {
786 if (document.activeElement != this) {
787 return;
788 }
789 term.keyPress(ev);
790 }, true);
791
792 on(term.element, 'keyup', term.focus.bind(term));
793
794 on(term.textarea, 'keydown', function(ev) {
795 term.keyDown(ev);
796 }, true);
797
798 on(term.textarea, 'keypress', function(ev) {
799 term.keyPress(ev);
800 // Truncate the textarea's value, since it is not needed
801 this.value = '';
802 }, true);
803
804 on(term.textarea, 'compositionstart', term.compositionHelper.compositionstart.bind(term.compositionHelper));
805 on(term.textarea, 'compositionupdate', term.compositionHelper.compositionupdate.bind(term.compositionHelper));
806 on(term.textarea, 'compositionend', term.compositionHelper.compositionend.bind(term.compositionHelper));
807 term.on('refresh', term.compositionHelper.updateCompositionElements.bind(term.compositionHelper));
808 };
809
810 /**
811 * Binds copy functionality to the given terminal.
812 * @static
813 */
814 Terminal.bindCopy = function(term) {
815 on(term.element, 'copy', function(ev) {
816 return; // temporary
817 });
818 };
819
820
821 /**
822 * Insert the given row to the terminal or produce a new one
823 * if no row argument is passed. Return the inserted row.
824 * @param {HTMLElement} row (optional) The row to append to the terminal.
825 */
826 Terminal.prototype.insertRow = function (row) {
827 if (typeof row != 'object') {
828 row = document.createElement('div');
829 }
830
831 this.rowContainer.appendChild(row);
832 this.children.push(row);
833
834 return row;
835 };
836
837 /**
838 * Opens the terminal within an element.
839 *
840 * @param {HTMLElement} parent The element to create the terminal within.
841 */
842 Terminal.prototype.open = function(parent) {
843 var self=this, i=0, div;
844
845 this.parent = parent || this.parent;
846
847 if (!this.parent) {
848 throw new Error('Terminal requires a parent element.');
849 }
850
851 /*
852 * Grab global elements
853 */
854 this.context = this.parent.ownerDocument.defaultView;
855 this.document = this.parent.ownerDocument;
856 this.body = this.document.getElementsByTagName('body')[0];
857
858 /*
859 * Parse User-Agent
860 */
861 if (this.context.navigator && this.context.navigator.userAgent) {
862 this.isMSIE = !!~this.context.navigator.userAgent.indexOf('MSIE');
863 }
864
865 /*
866 * Find the users platform. We use this to interpret the meta key
867 * and ISO third level shifts.
868 * http://stackoverflow.com/questions/19877924/what-is-the-list-of-possible-values-for-navigator-platform-as-of-today
869 */
870 if (this.context.navigator && this.context.navigator.platform) {
871 this.isMac = contains(
872 this.context.navigator.platform,
873 ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K']
874 );
875 this.isIpad = this.context.navigator.platform === 'iPad';
876 this.isIphone = this.context.navigator.platform === 'iPhone';
877 this.isMSWindows = contains(
878 this.context.navigator.platform,
879 ['Windows', 'Win16', 'Win32', 'WinCE']
880 );
881 }
882
883 /*
884 * Create main element container
885 */
886 this.element = this.document.createElement('div');
887 this.element.classList.add('terminal');
888 this.element.classList.add('xterm');
889 this.element.classList.add('xterm-theme-' + this.theme);
890
891 this.element.style.height
892 this.element.setAttribute('tabindex', 0);
893
894 this.viewportElement = document.createElement('div');
895 this.viewportElement.classList.add('xterm-viewport');
896 this.element.appendChild(this.viewportElement);
897 this.viewportScrollArea = document.createElement('div');
898 this.viewportScrollArea.classList.add('xterm-scroll-area');
899 this.viewportElement.appendChild(this.viewportScrollArea);
900
901 /*
902 * Create the container that will hold the lines of the terminal and then
903 * produce the lines the lines.
904 */
905 this.rowContainer = document.createElement('div');
906 this.rowContainer.classList.add('xterm-rows');
907 this.element.appendChild(this.rowContainer);
908 this.children = [];
909
910 /*
911 * Create the container that will hold helpers like the textarea for
912 * capturing DOM Events. Then produce the helpers.
913 */
914 this.helperContainer = document.createElement('div');
915 this.helperContainer.classList.add('xterm-helpers');
916 // TODO: This should probably be inserted once it's filled to prevent an additional layout
917 this.element.appendChild(this.helperContainer);
918 this.textarea = document.createElement('textarea');
919 this.textarea.classList.add('xterm-helper-textarea');
920 this.textarea.setAttribute('autocorrect', 'off');
921 this.textarea.setAttribute('autocapitalize', 'off');
922 this.textarea.setAttribute('spellcheck', 'false');
923 this.textarea.tabIndex = 0;
924 this.textarea.addEventListener('focus', function() {
925 self.emit('focus', {terminal: self});
926 });
927 this.textarea.addEventListener('blur', function() {
928 self.emit('blur', {terminal: self});
929 });
930 this.helperContainer.appendChild(this.textarea);
931
932 this.compositionView = document.createElement('div');
933 this.compositionView.classList.add('composition-view');
934 this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this);
935 this.helperContainer.appendChild(this.compositionView);
936
937 this.charMeasureElement = document.createElement('div');
938 this.charMeasureElement.classList.add('xterm-char-measure-element');
939 this.charMeasureElement.innerHTML = 'W';
940 this.helperContainer.appendChild(this.charMeasureElement);
941
942 for (; i < this.rows; i++) {
943 this.insertRow();
944 }
945 this.parent.appendChild(this.element);
946
947 this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasureElement);
948
949 // Draw the screen.
950 this.refresh(0, this.rows - 1);
951
952 // Initialize global actions that
953 // need to be taken on the document.
954 this.initGlobal();
955
956 // Ensure there is a Terminal.focus.
957 this.focus();
958
959 on(this.element, 'mouseup', function() {
960 var selection = document.getSelection(),
961 collapsed = selection.isCollapsed,
962 isRange = typeof collapsed == 'boolean' ? !collapsed : selection.type == 'Range';
963 if (!isRange) {
964 self.focus();
965 }
966 });
967
968 // Listen for mouse events and translate
969 // them into terminal mouse protocols.
970 this.bindMouse();
971
972 // Figure out whether boldness affects
973 // the character width of monospace fonts.
974 if (Terminal.brokenBold == null) {
975 Terminal.brokenBold = isBoldBroken(this.document);
976 }
977
978 this.emit('open');
979 };
980
981
982 /**
983 * Attempts to load an add-on using CommonJS or RequireJS (whichever is available).
984 * @param {string} addon The name of the addon to load
985 * @static
986 */
987 Terminal.loadAddon = function(addon, callback) {
988 if (typeof exports === 'object' && typeof module === 'object') {
989 // CommonJS
990 return require(__dirname + '/../addons/' + addon);
991 } else if (typeof define == 'function') {
992 // RequireJS
993 return require(['../addons/' + addon + '/' + addon], callback);
994 } else {
995 console.error('Cannot load a module without a CommonJS or RequireJS environment.');
996 return false;
997 }
998 };
999
1000
1001 /**
1002 * XTerm mouse events
1003 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
1004 * To better understand these
1005 * the xterm code is very helpful:
1006 * Relevant files:
1007 * button.c, charproc.c, misc.c
1008 * Relevant functions in xterm/button.c:
1009 * BtnCode, EmitButtonCode, EditorButton, SendMousePosition
1010 */
1011 Terminal.prototype.bindMouse = function() {
1012 var el = this.element, self = this, pressed = 32;
1013
1014 // mouseup, mousedown, wheel
1015 // left click: ^[[M 3<^[[M#3<
1016 // wheel up: ^[[M`3>
1017 function sendButton(ev) {
1018 var button
1019 , pos;
1020
1021 // get the xterm-style button
1022 button = getButton(ev);
1023
1024 // get mouse coordinates
1025 pos = getCoords(ev);
1026 if (!pos) return;
1027
1028 sendEvent(button, pos);
1029
1030 switch (ev.overrideType || ev.type) {
1031 case 'mousedown':
1032 pressed = button;
1033 break;
1034 case 'mouseup':
1035 // keep it at the left
1036 // button, just in case.
1037 pressed = 32;
1038 break;
1039 case 'wheel':
1040 // nothing. don't
1041 // interfere with
1042 // `pressed`.
1043 break;
1044 }
1045 }
1046
1047 // motion example of a left click:
1048 // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<
1049 function sendMove(ev) {
1050 var button = pressed
1051 , pos;
1052
1053 pos = getCoords(ev);
1054 if (!pos) return;
1055
1056 // buttons marked as motions
1057 // are incremented by 32
1058 button += 32;
1059
1060 sendEvent(button, pos);
1061 }
1062
1063 // encode button and
1064 // position to characters
1065 function encode(data, ch) {
1066 if (!self.utfMouse) {
1067 if (ch === 255) return data.push(0);
1068 if (ch > 127) ch = 127;
1069 data.push(ch);
1070 } else {
1071 if (ch === 2047) return data.push(0);
1072 if (ch < 127) {
1073 data.push(ch);
1074 } else {
1075 if (ch > 2047) ch = 2047;
1076 data.push(0xC0 | (ch >> 6));
1077 data.push(0x80 | (ch & 0x3F));
1078 }
1079 }
1080 }
1081
1082 // send a mouse event:
1083 // regular/utf8: ^[[M Cb Cx Cy
1084 // urxvt: ^[[ Cb ; Cx ; Cy M
1085 // sgr: ^[[ Cb ; Cx ; Cy M/m
1086 // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r
1087 // locator: CSI P e ; P b ; P r ; P c ; P p & w
1088 function sendEvent(button, pos) {
1089 // self.emit('mouse', {
1090 // x: pos.x - 32,
1091 // y: pos.x - 32,
1092 // button: button
1093 // });
1094
1095 if (self.vt300Mouse) {
1096 // NOTE: Unstable.
1097 // http://www.vt100.net/docs/vt3xx-gp/chapter15.html
1098 button &= 3;
1099 pos.x -= 32;
1100 pos.y -= 32;
1101 var data = '\x1b[24';
1102 if (button === 0) data += '1';
1103 else if (button === 1) data += '3';
1104 else if (button === 2) data += '5';
1105 else if (button === 3) return;
1106 else data += '0';
1107 data += '~[' + pos.x + ',' + pos.y + ']\r';
1108 self.send(data);
1109 return;
1110 }
1111
1112 if (self.decLocator) {
1113 // NOTE: Unstable.
1114 button &= 3;
1115 pos.x -= 32;
1116 pos.y -= 32;
1117 if (button === 0) button = 2;
1118 else if (button === 1) button = 4;
1119 else if (button === 2) button = 6;
1120 else if (button === 3) button = 3;
1121 self.send('\x1b['
1122 + button
1123 + ';'
1124 + (button === 3 ? 4 : 0)
1125 + ';'
1126 + pos.y
1127 + ';'
1128 + pos.x
1129 + ';'
1130 + (pos.page || 0)
1131 + '&w');
1132 return;
1133 }
1134
1135 if (self.urxvtMouse) {
1136 pos.x -= 32;
1137 pos.y -= 32;
1138 pos.x++;
1139 pos.y++;
1140 self.send('\x1b[' + button + ';' + pos.x + ';' + pos.y + 'M');
1141 return;
1142 }
1143
1144 if (self.sgrMouse) {
1145 pos.x -= 32;
1146 pos.y -= 32;
1147 self.send('\x1b[<'
1148 + ((button & 3) === 3 ? button & ~3 : button)
1149 + ';'
1150 + pos.x
1151 + ';'
1152 + pos.y
1153 + ((button & 3) === 3 ? 'm' : 'M'));
1154 return;
1155 }
1156
1157 var data = [];
1158
1159 encode(data, button);
1160 encode(data, pos.x);
1161 encode(data, pos.y);
1162
1163 self.send('\x1b[M' + String.fromCharCode.apply(String, data));
1164 }
1165
1166 function getButton(ev) {
1167 var button
1168 , shift
1169 , meta
1170 , ctrl
1171 , mod;
1172
1173 // two low bits:
1174 // 0 = left
1175 // 1 = middle
1176 // 2 = right
1177 // 3 = release
1178 // wheel up/down:
1179 // 1, and 2 - with 64 added
1180 switch (ev.overrideType || ev.type) {
1181 case 'mousedown':
1182 button = ev.button != null
1183 ? +ev.button
1184 : ev.which != null
1185 ? ev.which - 1
1186 : null;
1187
1188 if (self.isMSIE) {
1189 button = button === 1 ? 0 : button === 4 ? 1 : button;
1190 }
1191 break;
1192 case 'mouseup':
1193 button = 3;
1194 break;
1195 case 'DOMMouseScroll':
1196 button = ev.detail < 0
1197 ? 64
1198 : 65;
1199 break;
1200 case 'wheel':
1201 button = ev.wheelDeltaY > 0
1202 ? 64
1203 : 65;
1204 break;
1205 }
1206
1207 // next three bits are the modifiers:
1208 // 4 = shift, 8 = meta, 16 = control
1209 shift = ev.shiftKey ? 4 : 0;
1210 meta = ev.metaKey ? 8 : 0;
1211 ctrl = ev.ctrlKey ? 16 : 0;
1212 mod = shift | meta | ctrl;
1213
1214 // no mods
1215 if (self.vt200Mouse) {
1216 // ctrl only
1217 mod &= ctrl;
1218 } else if (!self.normalMouse) {
1219 mod = 0;
1220 }
1221
1222 // increment to SP
1223 button = (32 + (mod << 2)) + button;
1224
1225 return button;
1226 }
1227
1228 // mouse coordinates measured in cols/rows
1229 function getCoords(ev) {
1230 var x, y, w, h, el;
1231
1232 // ignore browsers without pageX for now
1233 if (ev.pageX == null) return;
1234
1235 x = ev.pageX;
1236 y = ev.pageY;
1237 el = self.element;
1238
1239 // should probably check offsetParent
1240 // but this is more portable
1241 while (el && el !== self.document.documentElement) {
1242 x -= el.offsetLeft;
1243 y -= el.offsetTop;
1244 el = 'offsetParent' in el
1245 ? el.offsetParent
1246 : el.parentNode;
1247 }
1248
1249 // convert to cols/rows
1250 w = self.element.clientWidth;
1251 h = self.element.clientHeight;
1252 x = Math.ceil((x / w) * self.cols);
1253 y = Math.ceil((y / h) * self.rows);
1254
1255 // be sure to avoid sending
1256 // bad positions to the program
1257 if (x < 0) x = 0;
1258 if (x > self.cols) x = self.cols;
1259 if (y < 0) y = 0;
1260 if (y > self.rows) y = self.rows;
1261
1262 // xterm sends raw bytes and
1263 // starts at 32 (SP) for each.
1264 x += 32;
1265 y += 32;
1266
1267 return {
1268 x: x,
1269 y: y,
1270 type: 'wheel'
1271 };
1272 }
1273
1274 on(el, 'mousedown', function(ev) {
1275 if (!self.mouseEvents) return;
1276
1277 // send the button
1278 sendButton(ev);
1279
1280 // ensure focus
1281 self.focus();
1282
1283 // fix for odd bug
1284 //if (self.vt200Mouse && !self.normalMouse) {
1285 if (self.vt200Mouse) {
1286 ev.overrideType = 'mouseup';
1287 sendButton(ev);
1288 return self.cancel(ev);
1289 }
1290
1291 // bind events
1292 if (self.normalMouse) on(self.document, 'mousemove', sendMove);
1293
1294 // x10 compatibility mode can't send button releases
1295 if (!self.x10Mouse) {
1296 on(self.document, 'mouseup', function up(ev) {
1297 sendButton(ev);
1298 if (self.normalMouse) off(self.document, 'mousemove', sendMove);
1299 off(self.document, 'mouseup', up);
1300 return self.cancel(ev);
1301 });
1302 }
1303
1304 return self.cancel(ev);
1305 });
1306
1307 //if (self.normalMouse) {
1308 // on(self.document, 'mousemove', sendMove);
1309 //}
1310
1311 on(el, 'wheel', function(ev) {
1312 if (!self.mouseEvents) return;
1313 if (self.x10Mouse
1314 || self.vt300Mouse
1315 || self.decLocator) return;
1316 sendButton(ev);
1317 return self.cancel(ev);
1318 });
1319
1320 // allow wheel scrolling in
1321 // the shell for example
1322 on(el, 'wheel', function(ev) {
1323 if (self.mouseEvents) return;
1324 if (self.applicationKeypad) return;
1325 self.viewport.onWheel(ev);
1326 return self.cancel(ev);
1327 });
1328 };
1329
1330 /**
1331 * Destroys the terminal.
1332 */
1333 Terminal.prototype.destroy = function() {
1334 this.readable = false;
1335 this.writable = false;
1336 this._events = {};
1337 this.handler = function() {};
1338 this.write = function() {};
1339 if (this.element.parentNode) {
1340 this.element.parentNode.removeChild(this.element);
1341 }
1342 //this.emit('close');
1343 };
1344
1345
1346 /**
1347 * Flags used to render terminal text properly
1348 */
1349 Terminal.flags = {
1350 BOLD: 1,
1351 UNDERLINE: 2,
1352 BLINK: 4,
1353 INVERSE: 8,
1354 INVISIBLE: 16
1355 }
1356
1357 /**
1358 * Refreshes (re-renders) terminal content within two rows (inclusive)
1359 *
1360 * Rendering Engine:
1361 *
1362 * In the screen buffer, each character is stored as a an array with a character
1363 * and a 32-bit integer:
1364 * - First value: a utf-16 character.
1365 * - Second value:
1366 * - Next 9 bits: background color (0-511).
1367 * - Next 9 bits: foreground color (0-511).
1368 * - Next 14 bits: a mask for misc. flags:
1369 * - 1=bold
1370 * - 2=underline
1371 * - 4=blink
1372 * - 8=inverse
1373 * - 16=invisible
1374 *
1375 * @param {number} start The row to start from (between 0 and terminal's height terminal - 1)
1376 * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1)
1377 * @param {boolean} queue Whether the refresh should ran right now or be queued
1378 */
1379 Terminal.prototype.refresh = function(start, end, queue) {
1380 var self = this;
1381
1382 // queue defaults to true
1383 queue = (typeof queue == 'undefined') ? true : queue;
1384
1385 /**
1386 * The refresh queue allows refresh to execute only approximately 30 times a second. For
1387 * commands that pass a significant amount of output to the write function, this prevents the
1388 * terminal from maxing out the CPU and making the UI unresponsive. While commands can still
1389 * run beyond what they do on the terminal, it is far better with a debounce in place as
1390 * every single terminal manipulation does not need to be constructed in the DOM.
1391 *
1392 * A side-effect of this is that it makes ^C to interrupt a process seem more responsive.
1393 */
1394 if (queue) {
1395 // If refresh should be queued, order the refresh and return.
1396 if (this._refreshIsQueued) {
1397 // If a refresh has already been queued, just order a full refresh next
1398 this._fullRefreshNext = true;
1399 } else {
1400 setTimeout(function () {
1401 self.refresh(start, end, false);
1402 }, 34)
1403 this._refreshIsQueued = true;
1404 }
1405 return;
1406 }
1407
1408 // If refresh should be run right now (not be queued), release the lock
1409 this._refreshIsQueued = false;
1410
1411 // If multiple refreshes were requested, make a full refresh.
1412 if (this._fullRefreshNext) {
1413 start = 0;
1414 end = this.rows - 1;
1415 this._fullRefreshNext = false // reset lock
1416 }
1417
1418 var x, y, i, line, out, ch, ch_width, width, data, attr, bg, fg, flags, row, parent, focused = document.activeElement;
1419
1420 // If this is a big refresh, remove the terminal rows from the DOM for faster calculations
1421 if (end - start >= this.rows / 2) {
1422 parent = this.element.parentNode;
1423 if (parent) {
1424 this.element.removeChild(this.rowContainer);
1425 }
1426 }
1427
1428 width = this.cols;
1429 y = start;
1430
1431 if (end >= this.rows.length) {
1432 this.log('`end` is too large. Most likely a bad CSR.');
1433 end = this.rows.length - 1;
1434 }
1435
1436 for (; y <= end; y++) {
1437 row = y + this.ydisp;
1438
1439 line = this.lines[row];
1440 out = '';
1441
1442 if (this.y === y - (this.ybase - this.ydisp)
1443 && this.cursorState
1444 && !this.cursorHidden) {
1445 x = this.x;
1446 } else {
1447 x = -1;
1448 }
1449
1450 attr = this.defAttr;
1451 i = 0;
1452
1453 for (; i < width; i++) {
1454 data = line[i][0];
1455 ch = line[i][1];
1456 ch_width = line[i][2];
1457 if (!ch_width)
1458 continue;
1459
1460 if (i === x) data = -1;
1461
1462 if (data !== attr) {
1463 if (attr !== this.defAttr) {
1464 out += '</span>';
1465 }
1466 if (data !== this.defAttr) {
1467 if (data === -1) {
1468 out += '<span class="reverse-video terminal-cursor';
1469 if (this.cursorBlink) {
1470 out += ' blinking';
1471 }
1472 out += '">';
1473 } else {
1474 var classNames = [];
1475
1476 bg = data & 0x1ff;
1477 fg = (data >> 9) & 0x1ff;
1478 flags = data >> 18;
1479
1480 if (flags & Terminal.flags.BOLD) {
1481 if (!Terminal.brokenBold) {
1482 classNames.push('xterm-bold');
1483 }
1484 // See: XTerm*boldColors
1485 if (fg < 8) fg += 8;
1486 }
1487
1488 if (flags & Terminal.flags.UNDERLINE) {
1489 classNames.push('xterm-underline');
1490 }
1491
1492 if (flags & Terminal.flags.BLINK) {
1493 classNames.push('xterm-blink');
1494 }
1495
1496 /**
1497 * If inverse flag is on, then swap the foreground and background variables.
1498 */
1499 if (flags & Terminal.flags.INVERSE) {
1500 /* One-line variable swap in JavaScript: http://stackoverflow.com/a/16201730 */
1501 bg = [fg, fg = bg][0];
1502 // Should inverse just be before the
1503 // above boldColors effect instead?
1504 if ((flags & 1) && fg < 8) fg += 8;
1505 }
1506
1507 if (flags & Terminal.flags.INVISIBLE) {
1508 classNames.push('xterm-hidden');
1509 }
1510
1511 /**
1512 * Weird situation: Invert flag used black foreground and white background results
1513 * in invalid background color, positioned at the 256 index of the 256 terminal
1514 * color map. Pin the colors manually in such a case.
1515 *
1516 * Source: https://github.com/sourcelair/xterm.js/issues/57
1517 */
1518 if (flags & Terminal.flags.INVERSE) {
1519 if (bg == 257) {
1520 bg = 15;
1521 }
1522 if (fg == 256) {
1523 fg = 0;
1524 }
1525 }
1526
1527 if (bg < 256) {
1528 classNames.push('xterm-bg-color-' + bg);
1529 }
1530
1531 if (fg < 256) {
1532 classNames.push('xterm-color-' + fg);
1533 }
1534
1535 out += '<span';
1536 if (classNames.length) {
1537 out += ' class="' + classNames.join(' ') + '"';
1538 }
1539 out += '>';
1540 }
1541 }
1542 }
1543
1544 switch (ch) {
1545 case '&':
1546 out += '&amp;';
1547 break;
1548 case '<':
1549 out += '&lt;';
1550 break;
1551 case '>':
1552 out += '&gt;';
1553 break;
1554 default:
1555 if (ch <= ' ') {
1556 out += '&nbsp;';
1557 } else {
1558 out += ch;
1559 }
1560 break;
1561 }
1562
1563 attr = data;
1564 }
1565
1566 if (attr !== this.defAttr) {
1567 out += '</span>';
1568 }
1569
1570 this.children[y].innerHTML = out;
1571 }
1572
1573 if (parent) {
1574 this.element.appendChild(this.rowContainer);
1575 }
1576
1577 this.emit('refresh', {element: this.element, start: start, end: end});
1578 };
1579
1580 /**
1581 * Display the cursor element
1582 */
1583 Terminal.prototype.showCursor = function() {
1584 if (!this.cursorState) {
1585 this.cursorState = 1;
1586 this.refresh(this.y, this.y);
1587 }
1588 };
1589
1590 /**
1591 * Scroll the terminal
1592 */
1593 Terminal.prototype.scroll = function() {
1594 var row;
1595
1596 if (++this.ybase === this.scrollback) {
1597 this.ybase = this.ybase / 2 | 0;
1598 this.lines = this.lines.slice(-(this.ybase + this.rows) + 1);
1599 }
1600
1601 this.ydisp = this.ybase;
1602
1603 // last line
1604 row = this.ybase + this.rows - 1;
1605
1606 // subtract the bottom scroll region
1607 row -= this.rows - 1 - this.scrollBottom;
1608
1609 if (row === this.lines.length) {
1610 // potential optimization:
1611 // pushing is faster than splicing
1612 // when they amount to the same
1613 // behavior.
1614 this.lines.push(this.blankLine());
1615 } else {
1616 // add our new line
1617 this.lines.splice(row, 0, this.blankLine());
1618 }
1619
1620 if (this.scrollTop !== 0) {
1621 if (this.ybase !== 0) {
1622 this.ybase--;
1623 this.ydisp = this.ybase;
1624 }
1625 this.lines.splice(this.ybase + this.scrollTop, 1);
1626 }
1627
1628 // this.maxRange();
1629 this.updateRange(this.scrollTop);
1630 this.updateRange(this.scrollBottom);
1631
1632 this.emit('scroll', this.ydisp);
1633 };
1634
1635 /**
1636 * Scroll the display of the terminal
1637 * @param {number} disp The number of lines to scroll down (negatives scroll up).
1638 * @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollDisp. This is used
1639 * to avoid unwanted events being handled by the veiwport when the event was triggered from the
1640 * viewport originally.
1641 */
1642 Terminal.prototype.scrollDisp = function(disp, suppressScrollEvent) {
1643 this.ydisp += disp;
1644
1645 if (this.ydisp > this.ybase) {
1646 this.ydisp = this.ybase;
1647 } else if (this.ydisp < 0) {
1648 this.ydisp = 0;
1649 }
1650
1651 if (!suppressScrollEvent) {
1652 this.emit('scroll', this.ydisp);
1653 }
1654
1655 this.refresh(0, this.rows - 1);
1656 };
1657
1658 /**
1659 * Writes text to the terminal.
1660 * @param {string} text The text to write to the terminal.
1661 */
1662 Terminal.prototype.write = function(data) {
1663 var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row;
1664
1665 this.refreshStart = this.y;
1666 this.refreshEnd = this.y;
1667
1668 if (this.ybase !== this.ydisp) {
1669 this.ydisp = this.ybase;
1670 this.emit('scroll', this.ydisp);
1671 this.maxRange();
1672 }
1673
1674 // apply leftover surrogate high from last write
1675 if (this.surrogate_high) {
1676 data = this.surrogate_high + data;
1677 this.surrogate_high = '';
1678 }
1679
1680 for (; i < l; i++) {
1681 ch = data[i];
1682
1683 // FIXME: higher chars than 0xa0 are not allowed in escape sequences
1684 // --> maybe move to default
1685 code = data.charCodeAt(i);
1686 if (0xD800 <= code && code <= 0xDBFF) {
1687 // we got a surrogate high
1688 // get surrogate low (next 2 bytes)
1689 low = data.charCodeAt(i+1);
1690 if (isNaN(low)) {
1691 // end of data stream, save surrogate high
1692 this.surrogate_high = ch;
1693 continue;
1694 }
1695 code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
1696 ch += data.charAt(i+1);
1697 }
1698 // surrogate low - already handled above
1699 if (0xDC00 <= code && code <= 0xDFFF)
1700 continue;
1701
1702 switch (this.state) {
1703 case normal:
1704 switch (ch) {
1705 case '\x07':
1706 this.bell();
1707 break;
1708
1709 // '\n', '\v', '\f'
1710 case '\n':
1711 case '\x0b':
1712 case '\x0c':
1713 if (this.convertEol) {
1714 this.x = 0;
1715 }
1716 this.y++;
1717 if (this.y > this.scrollBottom) {
1718 this.y--;
1719 this.scroll();
1720 }
1721 break;
1722
1723 // '\r'
1724 case '\r':
1725 this.x = 0;
1726 break;
1727
1728 // '\b'
1729 case '\x08':
1730 if (this.x > 0) {
1731 this.x--;
1732 }
1733 break;
1734
1735 // '\t'
1736 case '\t':
1737 this.x = this.nextStop();
1738 break;
1739
1740 // shift out
1741 case '\x0e':
1742 this.setgLevel(1);
1743 break;
1744
1745 // shift in
1746 case '\x0f':
1747 this.setgLevel(0);
1748 break;
1749
1750 // '\e'
1751 case '\x1b':
1752 this.state = escaped;
1753 break;
1754
1755 default:
1756 // ' '
1757 // calculate print space
1758 // expensive call, therefore we save width in line buffer
1759 ch_width = wcwidth(code);
1760
1761 if (ch >= ' ') {
1762 if (this.charset && this.charset[ch]) {
1763 ch = this.charset[ch];
1764 }
1765
1766 row = this.y + this.ybase;
1767
1768 // insert combining char in last cell
1769 // FIXME: needs handling after cursor jumps
1770 if (!ch_width && this.x) {
1771
1772 // dont overflow left
1773 if (this.lines[row][this.x-1]) {
1774 if (!this.lines[row][this.x-1][2]) {
1775
1776 // found empty cell after fullwidth, need to go 2 cells back
1777 if (this.lines[row][this.x-2])
1778 this.lines[row][this.x-2][1] += ch;
1779
1780 } else {
1781 this.lines[row][this.x-1][1] += ch;
1782 }
1783 this.updateRange(this.y);
1784 }
1785 break;
1786 }
1787
1788 // goto next line if ch would overflow
1789 // TODO: needs a global min terminal width of 2
1790 if (this.x+ch_width-1 >= this.cols) {
1791 // autowrap - DECAWM
1792 if (this.wraparoundMode) {
1793 this.x = 0;
1794 this.y++;
1795 if (this.y > this.scrollBottom) {
1796 this.y--;
1797 this.scroll();
1798 }
1799 } else {
1800 this.x = this.cols-1;
1801 if(ch_width===2) // FIXME: check for xterm behavior
1802 continue;
1803 }
1804 }
1805 row = this.y + this.ybase;
1806
1807 // insert mode: move characters to right
1808 if (this.insertMode) {
1809 // do this twice for a fullwidth char
1810 for (var moves=0; moves<ch_width; ++moves) {
1811 // remove last cell, if it's width is 0
1812 // we have to adjust the second last cell as well
1813 var removed = this.lines[this.y + this.ybase].pop();
1814 if (removed[2]===0
1815 && this.lines[row][this.cols-2]
1816 && this.lines[row][this.cols-2][2]===2)
1817 this.lines[row][this.cols-2] = [this.curAttr, ' ', 1];
1818
1819 // insert empty cell at cursor
1820 this.lines[row].splice(this.x, 0, [this.curAttr, ' ', 1]);
1821 }
1822 }
1823
1824 this.lines[row][this.x] = [this.curAttr, ch, ch_width];
1825 this.x++;
1826 this.updateRange(this.y);
1827
1828 // fullwidth char - set next cell width to zero and advance cursor
1829 if (ch_width===2) {
1830 this.lines[row][this.x] = [this.curAttr, '', 0];
1831 this.x++;
1832 }
1833 }
1834 break;
1835 }
1836 break;
1837 case escaped:
1838 switch (ch) {
1839 // ESC [ Control Sequence Introducer ( CSI is 0x9b).
1840 case '[':
1841 this.params = [];
1842 this.currentParam = 0;
1843 this.state = csi;
1844 break;
1845
1846 // ESC ] Operating System Command ( OSC is 0x9d).
1847 case ']':
1848 this.params = [];
1849 this.currentParam = 0;
1850 this.state = osc;
1851 break;
1852
1853 // ESC P Device Control String ( DCS is 0x90).
1854 case 'P':
1855 this.params = [];
1856 this.currentParam = 0;
1857 this.state = dcs;
1858 break;
1859
1860 // ESC _ Application Program Command ( APC is 0x9f).
1861 case '_':
1862 this.state = ignore;
1863 break;
1864
1865 // ESC ^ Privacy Message ( PM is 0x9e).
1866 case '^':
1867 this.state = ignore;
1868 break;
1869
1870 // ESC c Full Reset (RIS).
1871 case 'c':
1872 this.reset();
1873 break;
1874
1875 // ESC E Next Line ( NEL is 0x85).
1876 // ESC D Index ( IND is 0x84).
1877 case 'E':
1878 this.x = 0;
1879 ;
1880 case 'D':
1881 this.index();
1882 break;
1883
1884 // ESC M Reverse Index ( RI is 0x8d).
1885 case 'M':
1886 this.reverseIndex();
1887 break;
1888
1889 // ESC % Select default/utf-8 character set.
1890 // @ = default, G = utf-8
1891 case '%':
1892 //this.charset = null;
1893 this.setgLevel(0);
1894 this.setgCharset(0, Terminal.charsets.US);
1895 this.state = normal;
1896 i++;
1897 break;
1898
1899 // ESC (,),*,+,-,. Designate G0-G2 Character Set.
1900 case '(': // <-- this seems to get all the attention
1901 case ')':
1902 case '*':
1903 case '+':
1904 case '-':
1905 case '.':
1906 switch (ch) {
1907 case '(':
1908 this.gcharset = 0;
1909 break;
1910 case ')':
1911 this.gcharset = 1;
1912 break;
1913 case '*':
1914 this.gcharset = 2;
1915 break;
1916 case '+':
1917 this.gcharset = 3;
1918 break;
1919 case '-':
1920 this.gcharset = 1;
1921 break;
1922 case '.':
1923 this.gcharset = 2;
1924 break;
1925 }
1926 this.state = charset;
1927 break;
1928
1929 // Designate G3 Character Set (VT300).
1930 // A = ISO Latin-1 Supplemental.
1931 // Not implemented.
1932 case '/':
1933 this.gcharset = 3;
1934 this.state = charset;
1935 i--;
1936 break;
1937
1938 // ESC N
1939 // Single Shift Select of G2 Character Set
1940 // ( SS2 is 0x8e). This affects next character only.
1941 case 'N':
1942 break;
1943 // ESC O
1944 // Single Shift Select of G3 Character Set
1945 // ( SS3 is 0x8f). This affects next character only.
1946 case 'O':
1947 break;
1948 // ESC n
1949 // Invoke the G2 Character Set as GL (LS2).
1950 case 'n':
1951 this.setgLevel(2);
1952 break;
1953 // ESC o
1954 // Invoke the G3 Character Set as GL (LS3).
1955 case 'o':
1956 this.setgLevel(3);
1957 break;
1958 // ESC |
1959 // Invoke the G3 Character Set as GR (LS3R).
1960 case '|':
1961 this.setgLevel(3);
1962 break;
1963 // ESC }
1964 // Invoke the G2 Character Set as GR (LS2R).
1965 case '}':
1966 this.setgLevel(2);
1967 break;
1968 // ESC ~
1969 // Invoke the G1 Character Set as GR (LS1R).
1970 case '~':
1971 this.setgLevel(1);
1972 break;
1973
1974 // ESC 7 Save Cursor (DECSC).
1975 case '7':
1976 this.saveCursor();
1977 this.state = normal;
1978 break;
1979
1980 // ESC 8 Restore Cursor (DECRC).
1981 case '8':
1982 this.restoreCursor();
1983 this.state = normal;
1984 break;
1985
1986 // ESC # 3 DEC line height/width
1987 case '#':
1988 this.state = normal;
1989 i++;
1990 break;
1991
1992 // ESC H Tab Set (HTS is 0x88).
1993 case 'H':
1994 this.tabSet();
1995 break;
1996
1997 // ESC = Application Keypad (DECKPAM).
1998 case '=':
1999 this.log('Serial port requested application keypad.');
2000 this.applicationKeypad = true;
2001 this.viewport.setApplicationMode(true);
2002 this.state = normal;
2003 break;
2004
2005 // ESC > Normal Keypad (DECKPNM).
2006 case '>':
2007 this.log('Switching back to normal keypad.');
2008 this.applicationKeypad = false;
2009 this.viewport.setApplicationMode(false);
2010 this.state = normal;
2011 break;
2012
2013 default:
2014 this.state = normal;
2015 this.error('Unknown ESC control: %s.', ch);
2016 break;
2017 }
2018 break;
2019
2020 case charset:
2021 switch (ch) {
2022 case '0': // DEC Special Character and Line Drawing Set.
2023 cs = Terminal.charsets.SCLD;
2024 break;
2025 case 'A': // UK
2026 cs = Terminal.charsets.UK;
2027 break;
2028 case 'B': // United States (USASCII).
2029 cs = Terminal.charsets.US;
2030 break;
2031 case '4': // Dutch
2032 cs = Terminal.charsets.Dutch;
2033 break;
2034 case 'C': // Finnish
2035 case '5':
2036 cs = Terminal.charsets.Finnish;
2037 break;
2038 case 'R': // French
2039 cs = Terminal.charsets.French;
2040 break;
2041 case 'Q': // FrenchCanadian
2042 cs = Terminal.charsets.FrenchCanadian;
2043 break;
2044 case 'K': // German
2045 cs = Terminal.charsets.German;
2046 break;
2047 case 'Y': // Italian
2048 cs = Terminal.charsets.Italian;
2049 break;
2050 case 'E': // NorwegianDanish
2051 case '6':
2052 cs = Terminal.charsets.NorwegianDanish;
2053 break;
2054 case 'Z': // Spanish
2055 cs = Terminal.charsets.Spanish;
2056 break;
2057 case 'H': // Swedish
2058 case '7':
2059 cs = Terminal.charsets.Swedish;
2060 break;
2061 case '=': // Swiss
2062 cs = Terminal.charsets.Swiss;
2063 break;
2064 case '/': // ISOLatin (actually /A)
2065 cs = Terminal.charsets.ISOLatin;
2066 i++;
2067 break;
2068 default: // Default
2069 cs = Terminal.charsets.US;
2070 break;
2071 }
2072 this.setgCharset(this.gcharset, cs);
2073 this.gcharset = null;
2074 this.state = normal;
2075 break;
2076
2077 case osc:
2078 // OSC Ps ; Pt ST
2079 // OSC Ps ; Pt BEL
2080 // Set Text Parameters.
2081 if (ch === '\x1b' || ch === '\x07') {
2082 if (ch === '\x1b') i++;
2083
2084 this.params.push(this.currentParam);
2085
2086 switch (this.params[0]) {
2087 case 0:
2088 case 1:
2089 case 2:
2090 if (this.params[1]) {
2091 this.title = this.params[1];
2092 this.handleTitle(this.title);
2093 }
2094 break;
2095 case 3:
2096 // set X property
2097 break;
2098 case 4:
2099 case 5:
2100 // change dynamic colors
2101 break;
2102 case 10:
2103 case 11:
2104 case 12:
2105 case 13:
2106 case 14:
2107 case 15:
2108 case 16:
2109 case 17:
2110 case 18:
2111 case 19:
2112 // change dynamic ui colors
2113 break;
2114 case 46:
2115 // change log file
2116 break;
2117 case 50:
2118 // dynamic font
2119 break;
2120 case 51:
2121 // emacs shell
2122 break;
2123 case 52:
2124 // manipulate selection data
2125 break;
2126 case 104:
2127 case 105:
2128 case 110:
2129 case 111:
2130 case 112:
2131 case 113:
2132 case 114:
2133 case 115:
2134 case 116:
2135 case 117:
2136 case 118:
2137 // reset colors
2138 break;
2139 }
2140
2141 this.params = [];
2142 this.currentParam = 0;
2143 this.state = normal;
2144 } else {
2145 if (!this.params.length) {
2146 if (ch >= '0' && ch <= '9') {
2147 this.currentParam =
2148 this.currentParam * 10 + ch.charCodeAt(0) - 48;
2149 } else if (ch === ';') {
2150 this.params.push(this.currentParam);
2151 this.currentParam = '';
2152 }
2153 } else {
2154 this.currentParam += ch;
2155 }
2156 }
2157 break;
2158
2159 case csi:
2160 // '?', '>', '!'
2161 if (ch === '?' || ch === '>' || ch === '!') {
2162 this.prefix = ch;
2163 break;
2164 }
2165
2166 // 0 - 9
2167 if (ch >= '0' && ch <= '9') {
2168 this.currentParam = this.currentParam * 10 + ch.charCodeAt(0) - 48;
2169 break;
2170 }
2171
2172 // '$', '"', ' ', '\''
2173 if (ch === '$' || ch === '"' || ch === ' ' || ch === '\'') {
2174 this.postfix = ch;
2175 break;
2176 }
2177
2178 this.params.push(this.currentParam);
2179 this.currentParam = 0;
2180
2181 // ';'
2182 if (ch === ';') break;
2183
2184 this.state = normal;
2185
2186 switch (ch) {
2187 // CSI Ps A
2188 // Cursor Up Ps Times (default = 1) (CUU).
2189 case 'A':
2190 this.cursorUp(this.params);
2191 break;
2192
2193 // CSI Ps B
2194 // Cursor Down Ps Times (default = 1) (CUD).
2195 case 'B':
2196 this.cursorDown(this.params);
2197 break;
2198
2199 // CSI Ps C
2200 // Cursor Forward Ps Times (default = 1) (CUF).
2201 case 'C':
2202 this.cursorForward(this.params);
2203 break;
2204
2205 // CSI Ps D
2206 // Cursor Backward Ps Times (default = 1) (CUB).
2207 case 'D':
2208 this.cursorBackward(this.params);
2209 break;
2210
2211 // CSI Ps ; Ps H
2212 // Cursor Position [row;column] (default = [1,1]) (CUP).
2213 case 'H':
2214 this.cursorPos(this.params);
2215 break;
2216
2217 // CSI Ps J Erase in Display (ED).
2218 case 'J':
2219 this.eraseInDisplay(this.params);
2220 break;
2221
2222 // CSI Ps K Erase in Line (EL).
2223 case 'K':
2224 this.eraseInLine(this.params);
2225 break;
2226
2227 // CSI Pm m Character Attributes (SGR).
2228 case 'm':
2229 if (!this.prefix) {
2230 this.charAttributes(this.params);
2231 }
2232 break;
2233
2234 // CSI Ps n Device Status Report (DSR).
2235 case 'n':
2236 if (!this.prefix) {
2237 this.deviceStatus(this.params);
2238 }
2239 break;
2240
2241 /**
2242 * Additions
2243 */
2244
2245 // CSI Ps @
2246 // Insert Ps (Blank) Character(s) (default = 1) (ICH).
2247 case '@':
2248 this.insertChars(this.params);
2249 break;
2250
2251 // CSI Ps E
2252 // Cursor Next Line Ps Times (default = 1) (CNL).
2253 case 'E':
2254 this.cursorNextLine(this.params);
2255 break;
2256
2257 // CSI Ps F
2258 // Cursor Preceding Line Ps Times (default = 1) (CNL).
2259 case 'F':
2260 this.cursorPrecedingLine(this.params);
2261 break;
2262
2263 // CSI Ps G
2264 // Cursor Character Absolute [column] (default = [row,1]) (CHA).
2265 case 'G':
2266 this.cursorCharAbsolute(this.params);
2267 break;
2268
2269 // CSI Ps L
2270 // Insert Ps Line(s) (default = 1) (IL).
2271 case 'L':
2272 this.insertLines(this.params);
2273 break;
2274
2275 // CSI Ps M
2276 // Delete Ps Line(s) (default = 1) (DL).
2277 case 'M':
2278 this.deleteLines(this.params);
2279 break;
2280
2281 // CSI Ps P
2282 // Delete Ps Character(s) (default = 1) (DCH).
2283 case 'P':
2284 this.deleteChars(this.params);
2285 break;
2286
2287 // CSI Ps X
2288 // Erase Ps Character(s) (default = 1) (ECH).
2289 case 'X':
2290 this.eraseChars(this.params);
2291 break;
2292
2293 // CSI Pm ` Character Position Absolute
2294 // [column] (default = [row,1]) (HPA).
2295 case '`':
2296 this.charPosAbsolute(this.params);
2297 break;
2298
2299 // 141 61 a * HPR -
2300 // Horizontal Position Relative
2301 case 'a':
2302 this.HPositionRelative(this.params);
2303 break;
2304
2305 // CSI P s c
2306 // Send Device Attributes (Primary DA).
2307 // CSI > P s c
2308 // Send Device Attributes (Secondary DA)
2309 case 'c':
2310 this.sendDeviceAttributes(this.params);
2311 break;
2312
2313 // CSI Pm d
2314 // Line Position Absolute [row] (default = [1,column]) (VPA).
2315 case 'd':
2316 this.linePosAbsolute(this.params);
2317 break;
2318
2319 // 145 65 e * VPR - Vertical Position Relative
2320 case 'e':
2321 this.VPositionRelative(this.params);
2322 break;
2323
2324 // CSI Ps ; Ps f
2325 // Horizontal and Vertical Position [row;column] (default =
2326 // [1,1]) (HVP).
2327 case 'f':
2328 this.HVPosition(this.params);
2329 break;
2330
2331 // CSI Pm h Set Mode (SM).
2332 // CSI ? Pm h - mouse escape codes, cursor escape codes
2333 case 'h':
2334 this.setMode(this.params);
2335 break;
2336
2337 // CSI Pm l Reset Mode (RM).
2338 // CSI ? Pm l
2339 case 'l':
2340 this.resetMode(this.params);
2341 break;
2342
2343 // CSI Ps ; Ps r
2344 // Set Scrolling Region [top;bottom] (default = full size of win-
2345 // dow) (DECSTBM).
2346 // CSI ? Pm r
2347 case 'r':
2348 this.setScrollRegion(this.params);
2349 break;
2350
2351 // CSI s
2352 // Save cursor (ANSI.SYS).
2353 case 's':
2354 this.saveCursor(this.params);
2355 break;
2356
2357 // CSI u
2358 // Restore cursor (ANSI.SYS).
2359 case 'u':
2360 this.restoreCursor(this.params);
2361 break;
2362
2363 /**
2364 * Lesser Used
2365 */
2366
2367 // CSI Ps I
2368 // Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).
2369 case 'I':
2370 this.cursorForwardTab(this.params);
2371 break;
2372
2373 // CSI Ps S Scroll up Ps lines (default = 1) (SU).
2374 case 'S':
2375 this.scrollUp(this.params);
2376 break;
2377
2378 // CSI Ps T Scroll down Ps lines (default = 1) (SD).
2379 // CSI Ps ; Ps ; Ps ; Ps ; Ps T
2380 // CSI > Ps; Ps T
2381 case 'T':
2382 // if (this.prefix === '>') {
2383 // this.resetTitleModes(this.params);
2384 // break;
2385 // }
2386 // if (this.params.length > 2) {
2387 // this.initMouseTracking(this.params);
2388 // break;
2389 // }
2390 if (this.params.length < 2 && !this.prefix) {
2391 this.scrollDown(this.params);
2392 }
2393 break;
2394
2395 // CSI Ps Z
2396 // Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).
2397 case 'Z':
2398 this.cursorBackwardTab(this.params);
2399 break;
2400
2401 // CSI Ps b Repeat the preceding graphic character Ps times (REP).
2402 case 'b':
2403 this.repeatPrecedingCharacter(this.params);
2404 break;
2405
2406 // CSI Ps g Tab Clear (TBC).
2407 case 'g':
2408 this.tabClear(this.params);
2409 break;
2410
2411 // CSI Pm i Media Copy (MC).
2412 // CSI ? Pm i
2413 // case 'i':
2414 // this.mediaCopy(this.params);
2415 // break;
2416
2417 // CSI Pm m Character Attributes (SGR).
2418 // CSI > Ps; Ps m
2419 // case 'm': // duplicate
2420 // if (this.prefix === '>') {
2421 // this.setResources(this.params);
2422 // } else {
2423 // this.charAttributes(this.params);
2424 // }
2425 // break;
2426
2427 // CSI Ps n Device Status Report (DSR).
2428 // CSI > Ps n
2429 // case 'n': // duplicate
2430 // if (this.prefix === '>') {
2431 // this.disableModifiers(this.params);
2432 // } else {
2433 // this.deviceStatus(this.params);
2434 // }
2435 // break;
2436
2437 // CSI > Ps p Set pointer mode.
2438 // CSI ! p Soft terminal reset (DECSTR).
2439 // CSI Ps$ p
2440 // Request ANSI mode (DECRQM).
2441 // CSI ? Ps$ p
2442 // Request DEC private mode (DECRQM).
2443 // CSI Ps ; Ps " p
2444 case 'p':
2445 switch (this.prefix) {
2446 // case '>':
2447 // this.setPointerMode(this.params);
2448 // break;
2449 case '!':
2450 this.softReset(this.params);
2451 break;
2452 // case '?':
2453 // if (this.postfix === '$') {
2454 // this.requestPrivateMode(this.params);
2455 // }
2456 // break;
2457 // default:
2458 // if (this.postfix === '"') {
2459 // this.setConformanceLevel(this.params);
2460 // } else if (this.postfix === '$') {
2461 // this.requestAnsiMode(this.params);
2462 // }
2463 // break;
2464 }
2465 break;
2466
2467 // CSI Ps q Load LEDs (DECLL).
2468 // CSI Ps SP q
2469 // CSI Ps " q
2470 // case 'q':
2471 // if (this.postfix === ' ') {
2472 // this.setCursorStyle(this.params);
2473 // break;
2474 // }
2475 // if (this.postfix === '"') {
2476 // this.setCharProtectionAttr(this.params);
2477 // break;
2478 // }
2479 // this.loadLEDs(this.params);
2480 // break;
2481
2482 // CSI Ps ; Ps r
2483 // Set Scrolling Region [top;bottom] (default = full size of win-
2484 // dow) (DECSTBM).
2485 // CSI ? Pm r
2486 // CSI Pt; Pl; Pb; Pr; Ps$ r
2487 // case 'r': // duplicate
2488 // if (this.prefix === '?') {
2489 // this.restorePrivateValues(this.params);
2490 // } else if (this.postfix === '$') {
2491 // this.setAttrInRectangle(this.params);
2492 // } else {
2493 // this.setScrollRegion(this.params);
2494 // }
2495 // break;
2496
2497 // CSI s Save cursor (ANSI.SYS).
2498 // CSI ? Pm s
2499 // case 's': // duplicate
2500 // if (this.prefix === '?') {
2501 // this.savePrivateValues(this.params);
2502 // } else {
2503 // this.saveCursor(this.params);
2504 // }
2505 // break;
2506
2507 // CSI Ps ; Ps ; Ps t
2508 // CSI Pt; Pl; Pb; Pr; Ps$ t
2509 // CSI > Ps; Ps t
2510 // CSI Ps SP t
2511 // case 't':
2512 // if (this.postfix === '$') {
2513 // this.reverseAttrInRectangle(this.params);
2514 // } else if (this.postfix === ' ') {
2515 // this.setWarningBellVolume(this.params);
2516 // } else {
2517 // if (this.prefix === '>') {
2518 // this.setTitleModeFeature(this.params);
2519 // } else {
2520 // this.manipulateWindow(this.params);
2521 // }
2522 // }
2523 // break;
2524
2525 // CSI u Restore cursor (ANSI.SYS).
2526 // CSI Ps SP u
2527 // case 'u': // duplicate
2528 // if (this.postfix === ' ') {
2529 // this.setMarginBellVolume(this.params);
2530 // } else {
2531 // this.restoreCursor(this.params);
2532 // }
2533 // break;
2534
2535 // CSI Pt; Pl; Pb; Pr; Pp; Pt; Pl; Pp$ v
2536 // case 'v':
2537 // if (this.postfix === '$') {
2538 // this.copyRectagle(this.params);
2539 // }
2540 // break;
2541
2542 // CSI Pt ; Pl ; Pb ; Pr ' w
2543 // case 'w':
2544 // if (this.postfix === '\'') {
2545 // this.enableFilterRectangle(this.params);
2546 // }
2547 // break;
2548
2549 // CSI Ps x Request Terminal Parameters (DECREQTPARM).
2550 // CSI Ps x Select Attribute Change Extent (DECSACE).
2551 // CSI Pc; Pt; Pl; Pb; Pr$ x
2552 // case 'x':
2553 // if (this.postfix === '$') {
2554 // this.fillRectangle(this.params);
2555 // } else {
2556 // this.requestParameters(this.params);
2557 // //this.__(this.params);
2558 // }
2559 // break;
2560
2561 // CSI Ps ; Pu ' z
2562 // CSI Pt; Pl; Pb; Pr$ z
2563 // case 'z':
2564 // if (this.postfix === '\'') {
2565 // this.enableLocatorReporting(this.params);
2566 // } else if (this.postfix === '$') {
2567 // this.eraseRectangle(this.params);
2568 // }
2569 // break;
2570
2571 // CSI Pm ' {
2572 // CSI Pt; Pl; Pb; Pr$ {
2573 // case '{':
2574 // if (this.postfix === '\'') {
2575 // this.setLocatorEvents(this.params);
2576 // } else if (this.postfix === '$') {
2577 // this.selectiveEraseRectangle(this.params);
2578 // }
2579 // break;
2580
2581 // CSI Ps ' |
2582 // case '|':
2583 // if (this.postfix === '\'') {
2584 // this.requestLocatorPosition(this.params);
2585 // }
2586 // break;
2587
2588 // CSI P m SP }
2589 // Insert P s Column(s) (default = 1) (DECIC), VT420 and up.
2590 // case '}':
2591 // if (this.postfix === ' ') {
2592 // this.insertColumns(this.params);
2593 // }
2594 // break;
2595
2596 // CSI P m SP ~
2597 // Delete P s Column(s) (default = 1) (DECDC), VT420 and up
2598 // case '~':
2599 // if (this.postfix === ' ') {
2600 // this.deleteColumns(this.params);
2601 // }
2602 // break;
2603
2604 default:
2605 this.error('Unknown CSI code: %s.', ch);
2606 break;
2607 }
2608
2609 this.prefix = '';
2610 this.postfix = '';
2611 break;
2612
2613 case dcs:
2614 if (ch === '\x1b' || ch === '\x07') {
2615 if (ch === '\x1b') i++;
2616
2617 switch (this.prefix) {
2618 // User-Defined Keys (DECUDK).
2619 case '':
2620 break;
2621
2622 // Request Status String (DECRQSS).
2623 // test: echo -e '\eP$q"p\e\\'
2624 case '$q':
2625 var pt = this.currentParam
2626 , valid = false;
2627
2628 switch (pt) {
2629 // DECSCA
2630 case '"q':
2631 pt = '0"q';
2632 break;
2633
2634 // DECSCL
2635 case '"p':
2636 pt = '61"p';
2637 break;
2638
2639 // DECSTBM
2640 case 'r':
2641 pt = ''
2642 + (this.scrollTop + 1)
2643 + ';'
2644 + (this.scrollBottom + 1)
2645 + 'r';
2646 break;
2647
2648 // SGR
2649 case 'm':
2650 pt = '0m';
2651 break;
2652
2653 default:
2654 this.error('Unknown DCS Pt: %s.', pt);
2655 pt = '';
2656 break;
2657 }
2658
2659 this.send('\x1bP' + +valid + '$r' + pt + '\x1b\\');
2660 break;
2661
2662 // Set Termcap/Terminfo Data (xterm, experimental).
2663 case '+p':
2664 break;
2665
2666 // Request Termcap/Terminfo String (xterm, experimental)
2667 // Regular xterm does not even respond to this sequence.
2668 // This can cause a small glitch in vim.
2669 // test: echo -ne '\eP+q6b64\e\\'
2670 case '+q':
2671 var pt = this.currentParam
2672 , valid = false;
2673
2674 this.send('\x1bP' + +valid + '+r' + pt + '\x1b\\');
2675 break;
2676
2677 default:
2678 this.error('Unknown DCS prefix: %s.', this.prefix);
2679 break;
2680 }
2681
2682 this.currentParam = 0;
2683 this.prefix = '';
2684 this.state = normal;
2685 } else if (!this.currentParam) {
2686 if (!this.prefix && ch !== '$' && ch !== '+') {
2687 this.currentParam = ch;
2688 } else if (this.prefix.length === 2) {
2689 this.currentParam = ch;
2690 } else {
2691 this.prefix += ch;
2692 }
2693 } else {
2694 this.currentParam += ch;
2695 }
2696 break;
2697
2698 case ignore:
2699 // For PM and APC.
2700 if (ch === '\x1b' || ch === '\x07') {
2701 if (ch === '\x1b') i++;
2702 this.state = normal;
2703 }
2704 break;
2705 }
2706 }
2707
2708 this.updateRange(this.y);
2709 this.refresh(this.refreshStart, this.refreshEnd);
2710 };
2711
2712 /**
2713 * Writes text to the terminal, followed by a break line character (\n).
2714 * @param {string} text The text to write to the terminal.
2715 */
2716 Terminal.prototype.writeln = function(data) {
2717 this.write(data + '\r\n');
2718 };
2719
2720 /**
2721 * Attaches a custom keydown handler which is run before keys are processed, giving consumers of
2722 * xterm.js ultimate control as to what keys should be processed by the terminal and what keys
2723 * should not.
2724 * @param {function} customKeydownHandler The custom KeyboardEvent handler to attach. This is a
2725 * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent
2726 * the default action. The function returns whether the event should be processed by xterm.js.
2727 */
2728 Terminal.prototype.attachCustomKeydownHandler = function(customKeydownHandler) {
2729 this.customKeydownHandler = customKeydownHandler;
2730 }
2731
2732 /**
2733 * Handle a keydown event
2734 * Key Resources:
2735 * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
2736 * @param {KeyboardEvent} ev The keydown event to be handled.
2737 */
2738 Terminal.prototype.keyDown = function(ev) {
2739 if (this.customKeydownHandler && this.customKeydownHandler(ev) === false) {
2740 return false;
2741 }
2742
2743 if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {
2744 return false;
2745 }
2746
2747 var self = this;
2748 var result = this.evaluateKeyEscapeSequence(ev);
2749
2750 if (result.scrollDisp) {
2751 this.scrollDisp(result.scrollDisp);
2752 return this.cancel(ev);
2753 }
2754
2755 if (isThirdLevelShift(this, ev)) {
2756 return true;
2757 }
2758
2759 if (result.cancel ) {
2760 // The event is canceled at the end already, is this necessary?
2761 this.cancel(ev, true);
2762 }
2763
2764 if (!result.key) {
2765 return true;
2766 }
2767
2768 this.emit('keydown', ev);
2769 this.emit('key', result.key, ev);
2770 this.showCursor();
2771 this.handler(result.key);
2772
2773 return this.cancel(ev, true);
2774 };
2775
2776 /**
2777 * Returns an object that determines how a KeyboardEvent should be handled. The key of the
2778 * returned value is the new key code to pass to the PTY.
2779 *
2780 * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
2781 * @param {KeyboardEvent} ev The keyboard event to be translated to key escape sequence.
2782 */
2783 Terminal.prototype.evaluateKeyEscapeSequence = function(ev) {
2784 var result = {
2785 // Whether to cancel event propogation (NOTE: this may not be needed since the event is
2786 // canceled at the end of keyDown
2787 cancel: false,
2788 // The new key even to emit
2789 key: undefined,
2790 // The number of characters to scroll, if this is defined it will cancel the event
2791 scrollDisp: undefined
2792 };
2793 var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3;
2794 switch (ev.keyCode) {
2795 // backspace
2796 case 8:
2797 if (ev.shiftKey) {
2798 result.key = '\x08'; // ^H
2799 break;
2800 }
2801 result.key = '\x7f'; // ^?
2802 break;
2803 // tab
2804 case 9:
2805 if (ev.shiftKey) {
2806 result.key = '\x1b[Z';
2807 break;
2808 }
2809 result.key = '\t';
2810 result.cancel = true;
2811 break;
2812 // return/enter
2813 case 13:
2814 result.key = '\r';
2815 result.cancel = true;
2816 break;
2817 // escape
2818 case 27:
2819 result.key = '\x1b';
2820 result.cancel = true;
2821 break;
2822 // left-arrow
2823 case 37:
2824 if (modifiers) {
2825 result.key = '\x1b[1;' + (modifiers + 1) + 'D';
2826 // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards
2827 // http://unix.stackexchange.com/a/108106
2828 if (result.key == '\x1b[1;3D') {
2829 result.key = '\x1b[1;5D';
2830 }
2831 } else if (this.applicationCursor) {
2832 result.key = '\x1bOD';
2833 } else {
2834 result.key = '\x1b[D';
2835 }
2836 break;
2837 // right-arrow
2838 case 39:
2839 if (modifiers) {
2840 result.key = '\x1b[1;' + (modifiers + 1) + 'C';
2841 // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward
2842 // http://unix.stackexchange.com/a/108106
2843 if (result.key == '\x1b[1;3C') {
2844 result.key = '\x1b[1;5C';
2845 }
2846 } else if (this.applicationCursor) {
2847 result.key = '\x1bOC';
2848 } else {
2849 result.key = '\x1b[C';
2850 }
2851 break;
2852 // up-arrow
2853 case 38:
2854 if (modifiers) {
2855 result.key = '\x1b[1;' + (modifiers + 1) + 'A';
2856 // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow
2857 // http://unix.stackexchange.com/a/108106
2858 if (result.key == '\x1b[1;3A') {
2859 result.key = '\x1b[1;5A';
2860 }
2861 } else if (this.applicationCursor) {
2862 result.key = '\x1bOA';
2863 } else {
2864 result.key = '\x1b[A';
2865 }
2866 break;
2867 // down-arrow
2868 case 40:
2869 if (modifiers) {
2870 result.key = '\x1b[1;' + (modifiers + 1) + 'B';
2871 // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow
2872 // http://unix.stackexchange.com/a/108106
2873 if (result.key == '\x1b[1;3B') {
2874 result.key = '\x1b[1;5B';
2875 }
2876 } else if (this.applicationCursor) {
2877 result.key = '\x1bOB';
2878 } else {
2879 result.key = '\x1b[B';
2880 }
2881 break;
2882 // insert
2883 case 45:
2884 if (!ev.shiftKey && !ev.ctrlKey) {
2885 // <Ctrl> or <Shift> + <Insert> are used to
2886 // copy-paste on some systems.
2887 result.key = '\x1b[2~';
2888 }
2889 break;
2890 // delete
2891 case 46: result.key = '\x1b[3~'; break;
2892 // home
2893 case 36:
2894 if (modifiers)
2895 result.key = '\x1b[1;' + (modifiers + 1) + 'H';
2896 else if (this.applicationCursor)
2897 result.key = '\x1bOH';
2898 else
2899 result.key = '\x1b[H';
2900 break;
2901 // end
2902 case 35:
2903 if (modifiers)
2904 result.key = '\x1b[1;' + (modifiers + 1) + 'F';
2905 else if (this.applicationCursor)
2906 result.key = '\x1bOF';
2907 else
2908 result.key = '\x1b[F';
2909 break;
2910 // page up
2911 case 33:
2912 if (ev.shiftKey) {
2913 result.scrollDisp = -(this.rows - 1);
2914 } else {
2915 result.key = '\x1b[5~';
2916 }
2917 break;
2918 // page down
2919 case 34:
2920 if (ev.shiftKey) {
2921 result.scrollDisp = this.rows - 1;
2922 } else {
2923 result.key = '\x1b[6~';
2924 }
2925 break;
2926 // F1-F12
2927 case 112: result.key = '\x1bOP'; break;
2928 case 113: result.key = '\x1bOQ'; break;
2929 case 114: result.key = '\x1bOR'; break;
2930 case 115: result.key = '\x1bOS'; break;
2931 case 116: result.key = '\x1b[15~'; break;
2932 case 117: result.key = '\x1b[17~'; break;
2933 case 118: result.key = '\x1b[18~'; break;
2934 case 119: result.key = '\x1b[19~'; break;
2935 case 120: result.key = '\x1b[20~'; break;
2936 case 121: result.key = '\x1b[21~'; break;
2937 case 122: result.key = '\x1b[23~'; break;
2938 case 123: result.key = '\x1b[24~'; break;
2939 default:
2940 // a-z and space
2941 if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
2942 if (ev.keyCode >= 65 && ev.keyCode <= 90) {
2943 result.key = String.fromCharCode(ev.keyCode - 64);
2944 } else if (ev.keyCode === 32) {
2945 // NUL
2946 result.key = String.fromCharCode(0);
2947 } else if (ev.keyCode >= 51 && ev.keyCode <= 55) {
2948 // escape, file sep, group sep, record sep, unit sep
2949 result.key = String.fromCharCode(ev.keyCode - 51 + 27);
2950 } else if (ev.keyCode === 56) {
2951 // delete
2952 result.key = String.fromCharCode(127);
2953 } else if (ev.keyCode === 219) {
2954 // ^[ - escape
2955 result.key = String.fromCharCode(27);
2956 } else if (ev.keyCode === 221) {
2957 // ^] - group sep
2958 result.key = String.fromCharCode(29);
2959 }
2960 } else if (!this.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) {
2961 // On Mac this is a third level shift. Use <Esc> instead.
2962 if (ev.keyCode >= 65 && ev.keyCode <= 90) {
2963 result.key = '\x1b' + String.fromCharCode(ev.keyCode + 32);
2964 } else if (ev.keyCode === 192) {
2965 result.key = '\x1b`';
2966 } else if (ev.keyCode >= 48 && ev.keyCode <= 57) {
2967 result.key = '\x1b' + (ev.keyCode - 48);
2968 }
2969 }
2970 break;
2971 }
2972 return result;
2973 };
2974
2975 /**
2976 * Set the G level of the terminal
2977 * @param g
2978 */
2979 Terminal.prototype.setgLevel = function(g) {
2980 this.glevel = g;
2981 this.charset = this.charsets[g];
2982 };
2983
2984 /**
2985 * Set the charset for the given G level of the terminal
2986 * @param g
2987 * @param charset
2988 */
2989 Terminal.prototype.setgCharset = function(g, charset) {
2990 this.charsets[g] = charset;
2991 if (this.glevel === g) {
2992 this.charset = charset;
2993 }
2994 };
2995
2996 /**
2997 * Handle a keypress event.
2998 * Key Resources:
2999 * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
3000 * @param {KeyboardEvent} ev The keypress event to be handled.
3001 */
3002 Terminal.prototype.keyPress = function(ev) {
3003 var key;
3004
3005 this.cancel(ev);
3006
3007 if (ev.charCode) {
3008 key = ev.charCode;
3009 } else if (ev.which == null) {
3010 key = ev.keyCode;
3011 } else if (ev.which !== 0 && ev.charCode !== 0) {
3012 key = ev.which;
3013 } else {
3014 return false;
3015 }
3016
3017 if (!key || (
3018 (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev)
3019 )) {
3020 return false;
3021 }
3022
3023 key = String.fromCharCode(key);
3024
3025 this.emit('keypress', key, ev);
3026 this.emit('key', key, ev);
3027 this.showCursor();
3028 this.handler(key);
3029
3030 return false;
3031 };
3032
3033 /**
3034 * Send data for handling to the terminal
3035 * @param {string} data
3036 */
3037 Terminal.prototype.send = function(data) {
3038 var self = this;
3039
3040 if (!this.queue) {
3041 setTimeout(function() {
3042 self.handler(self.queue);
3043 self.queue = '';
3044 }, 1);
3045 }
3046
3047 this.queue += data;
3048 };
3049
3050 /**
3051 * Ring the bell.
3052 * Note: We could do sweet things with webaudio here
3053 */
3054 Terminal.prototype.bell = function() {
3055 if (!this.visualBell) return;
3056 var self = this;
3057 this.element.style.borderColor = 'white';
3058 setTimeout(function() {
3059 self.element.style.borderColor = '';
3060 }, 10);
3061 if (this.popOnBell) this.focus();
3062 };
3063
3064 /**
3065 * Log the current state to the console.
3066 */
3067 Terminal.prototype.log = function() {
3068 if (!this.debug) return;
3069 if (!this.context.console || !this.context.console.log) return;
3070 var args = Array.prototype.slice.call(arguments);
3071 this.context.console.log.apply(this.context.console, args);
3072 };
3073
3074 /**
3075 * Log the current state as error to the console.
3076 */
3077 Terminal.prototype.error = function() {
3078 if (!this.debug) return;
3079 if (!this.context.console || !this.context.console.error) return;
3080 var args = Array.prototype.slice.call(arguments);
3081 this.context.console.error.apply(this.context.console, args);
3082 };
3083
3084 /**
3085 * Resizes the terminal.
3086 *
3087 * @param {number} x The number of columns to resize to.
3088 * @param {number} y The number of rows to resize to.
3089 */
3090 Terminal.prototype.resize = function(x, y) {
3091 var line
3092 , el
3093 , i
3094 , j
3095 , ch
3096 , addToY;
3097
3098 if (x === this.cols && y === this.rows) {
3099 return;
3100 }
3101
3102 if (x < 1) x = 1;
3103 if (y < 1) y = 1;
3104
3105 // resize cols
3106 j = this.cols;
3107 if (j < x) {
3108 ch = [this.defAttr, ' ', 1]; // does xterm use the default attr?
3109 i = this.lines.length;
3110 while (i--) {
3111 while (this.lines[i].length < x) {
3112 this.lines[i].push(ch);
3113 }
3114 }
3115 } else { // (j > x)
3116 i = this.lines.length;
3117 while (i--) {
3118 while (this.lines[i].length > x) {
3119 this.lines[i].pop();
3120 }
3121 }
3122 }
3123 this.setupStops(j);
3124 this.cols = x;
3125
3126 // resize rows
3127 j = this.rows;
3128 addToY = 0;
3129 if (j < y) {
3130 el = this.element;
3131 while (j++ < y) {
3132 // y is rows, not this.y
3133 if (this.lines.length < y + this.ybase) {
3134 if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
3135 // There is room above the buffer and there are no empty elements below the line,
3136 // scroll up
3137 this.ybase--;
3138 addToY++
3139 if (this.ydisp > 0) {
3140 // Viewport is at the top of the buffer, must increase downwards
3141 this.ydisp--;
3142 }
3143 } else {
3144 // Add a blank line if there is no buffer left at the top to scroll to, or if there
3145 // are blank lines after the cursor
3146 this.lines.push(this.blankLine());
3147 }
3148 }
3149 if (this.children.length < y) {
3150 this.insertRow();
3151 }
3152 }
3153 } else { // (j > y)
3154 while (j-- > y) {
3155 if (this.lines.length > y + this.ybase) {
3156 if (this.lines.length > this.ybase + this.y + 1) {
3157 // The line is a blank line below the cursor, remove it
3158 this.lines.pop();
3159 } else {
3160 // The line is the cursor, scroll down
3161 this.ybase++;
3162 this.ydisp++;
3163 }
3164 }
3165 if (this.children.length > y) {
3166 el = this.children.shift();
3167 if (!el) continue;
3168 el.parentNode.removeChild(el);
3169 }
3170 }
3171 }
3172 this.rows = y;
3173
3174 /*
3175 * Make sure that the cursor stays on screen
3176 */
3177 if (this.y >= y) {
3178 this.y = y - 1;
3179 }
3180 if (addToY) {
3181 this.y += addToY;
3182 }
3183
3184 if (this.x >= x) {
3185 this.x = x - 1;
3186 }
3187
3188 this.scrollTop = 0;
3189 this.scrollBottom = y - 1;
3190
3191 this.refresh(0, this.rows - 1);
3192
3193 this.normal = null;
3194
3195 this.emit('resize', {terminal: this, cols: x, rows: y});
3196 };
3197
3198 /**
3199 * Updates the range of rows to refresh
3200 * @param {number} y The number of rows to refresh next.
3201 */
3202 Terminal.prototype.updateRange = function(y) {
3203 if (y < this.refreshStart) this.refreshStart = y;
3204 if (y > this.refreshEnd) this.refreshEnd = y;
3205 // if (y > this.refreshEnd) {
3206 // this.refreshEnd = y;
3207 // if (y > this.rows - 1) {
3208 // this.refreshEnd = this.rows - 1;
3209 // }
3210 // }
3211 };
3212
3213 /**
3214 * Set the range of refreshing to the maximyum value
3215 */
3216 Terminal.prototype.maxRange = function() {
3217 this.refreshStart = 0;
3218 this.refreshEnd = this.rows - 1;
3219 };
3220
3221
3222
3223 /**
3224 * Setup the tab stops.
3225 * @param {number} i
3226 */
3227 Terminal.prototype.setupStops = function(i) {
3228 if (i != null) {
3229 if (!this.tabs[i]) {
3230 i = this.prevStop(i);
3231 }
3232 } else {
3233 this.tabs = {};
3234 i = 0;
3235 }
3236
3237 for (; i < this.cols; i += 8) {
3238 this.tabs[i] = true;
3239 }
3240 };
3241
3242
3243 /**
3244 * Move the cursor to the previous tab stop from the given position (default is current).
3245 * @param {number} x The position to move the cursor to the previous tab stop.
3246 */
3247 Terminal.prototype.prevStop = function(x) {
3248 if (x == null) x = this.x;
3249 while (!this.tabs[--x] && x > 0);
3250 return x >= this.cols
3251 ? this.cols - 1
3252 : x < 0 ? 0 : x;
3253 };
3254
3255
3256 /**
3257 * Move the cursor one tab stop forward from the given position (default is current).
3258 * @param {number} x The position to move the cursor one tab stop forward.
3259 */
3260 Terminal.prototype.nextStop = function(x) {
3261 if (x == null) x = this.x;
3262 while (!this.tabs[++x] && x < this.cols);
3263 return x >= this.cols
3264 ? this.cols - 1
3265 : x < 0 ? 0 : x;
3266 };
3267
3268
3269 /**
3270 * Erase in the identified line everything from "x" to the end of the line (right).
3271 * @param {number} x The column from which to start erasing to the end of the line.
3272 * @param {number} y The line in which to operate.
3273 */
3274 Terminal.prototype.eraseRight = function(x, y) {
3275 var line = this.lines[this.ybase + y]
3276 , ch = [this.eraseAttr(), ' ', 1]; // xterm
3277
3278
3279 for (; x < this.cols; x++) {
3280 line[x] = ch;
3281 }
3282
3283 this.updateRange(y);
3284 };
3285
3286
3287
3288 /**
3289 * Erase in the identified line everything from "x" to the start of the line (left).
3290 * @param {number} x The column from which to start erasing to the start of the line.
3291 * @param {number} y The line in which to operate.
3292 */
3293 Terminal.prototype.eraseLeft = function(x, y) {
3294 var line = this.lines[this.ybase + y]
3295 , ch = [this.eraseAttr(), ' ', 1]; // xterm
3296
3297 x++;
3298 while (x--) line[x] = ch;
3299
3300 this.updateRange(y);
3301 };
3302
3303
3304 /**
3305 * Erase all content in the given line
3306 * @param {number} y The line to erase all of its contents.
3307 */
3308 Terminal.prototype.eraseLine = function(y) {
3309 this.eraseRight(0, y);
3310 };
3311
3312
3313 /**
3314 * Return the data array of a blank line/
3315 * @param {number} cur First bunch of data for each "blank" character.
3316 */
3317 Terminal.prototype.blankLine = function(cur) {
3318 var attr = cur
3319 ? this.eraseAttr()
3320 : this.defAttr;
3321
3322 var ch = [attr, ' ', 1] // width defaults to 1 halfwidth character
3323 , line = []
3324 , i = 0;
3325
3326 for (; i < this.cols; i++) {
3327 line[i] = ch;
3328 }
3329
3330 return line;
3331 };
3332
3333
3334 /**
3335 * If cur return the back color xterm feature attribute. Else return defAttr.
3336 * @param {object} cur
3337 */
3338 Terminal.prototype.ch = function(cur) {
3339 return cur
3340 ? [this.eraseAttr(), ' ', 1]
3341 : [this.defAttr, ' ', 1];
3342 };
3343
3344
3345 /**
3346 * Evaluate if the current erminal is the given argument.
3347 * @param {object} term The terminal to evaluate
3348 */
3349 Terminal.prototype.is = function(term) {
3350 var name = this.termName;
3351 return (name + '').indexOf(term) === 0;
3352 };
3353
3354
3355 /**
3356 * Emit the 'data' event and populate the given data.
3357 * @param {string} data The data to populate in the event.
3358 */
3359 Terminal.prototype.handler = function(data) {
3360 this.emit('data', data);
3361 };
3362
3363
3364 /**
3365 * Emit the 'title' event and populate the given title.
3366 * @param {string} title The title to populate in the event.
3367 */
3368 Terminal.prototype.handleTitle = function(title) {
3369 this.emit('title', title);
3370 };
3371
3372
3373 /**
3374 * ESC
3375 */
3376
3377 /**
3378 * ESC D Index (IND is 0x84).
3379 */
3380 Terminal.prototype.index = function() {
3381 this.y++;
3382 if (this.y > this.scrollBottom) {
3383 this.y--;
3384 this.scroll();
3385 }
3386 this.state = normal;
3387 };
3388
3389
3390 /**
3391 * ESC M Reverse Index (RI is 0x8d).
3392 */
3393 Terminal.prototype.reverseIndex = function() {
3394 var j;
3395 this.y--;
3396 if (this.y < this.scrollTop) {
3397 this.y++;
3398 // possibly move the code below to term.reverseScroll();
3399 // test: echo -ne '\e[1;1H\e[44m\eM\e[0m'
3400 // blankLine(true) is xterm/linux behavior
3401 this.lines.splice(this.y + this.ybase, 0, this.blankLine(true));
3402 j = this.rows - 1 - this.scrollBottom;
3403 this.lines.splice(this.rows - 1 + this.ybase - j + 1, 1);
3404 // this.maxRange();
3405 this.updateRange(this.scrollTop);
3406 this.updateRange(this.scrollBottom);
3407 }
3408 this.state = normal;
3409 };
3410
3411
3412 /**
3413 * ESC c Full Reset (RIS).
3414 */
3415 Terminal.prototype.reset = function() {
3416 this.options.rows = this.rows;
3417 this.options.cols = this.cols;
3418 var customKeydownHandler = this.customKeydownHandler;
3419 Terminal.call(this, this.options);
3420 this.customKeydownHandler = customKeydownHandler;
3421 this.refresh(0, this.rows - 1);
3422 };
3423
3424
3425 /**
3426 * ESC H Tab Set (HTS is 0x88).
3427 */
3428 Terminal.prototype.tabSet = function() {
3429 this.tabs[this.x] = true;
3430 this.state = normal;
3431 };
3432
3433
3434 /**
3435 * CSI
3436 */
3437
3438 /**
3439 * CSI Ps A
3440 * Cursor Up Ps Times (default = 1) (CUU).
3441 */
3442 Terminal.prototype.cursorUp = function(params) {
3443 var param = params[0];
3444 if (param < 1) param = 1;
3445 this.y -= param;
3446 if (this.y < 0) this.y = 0;
3447 };
3448
3449
3450 /**
3451 * CSI Ps B
3452 * Cursor Down Ps Times (default = 1) (CUD).
3453 */
3454 Terminal.prototype.cursorDown = function(params) {
3455 var param = params[0];
3456 if (param < 1) param = 1;
3457 this.y += param;
3458 if (this.y >= this.rows) {
3459 this.y = this.rows - 1;
3460 }
3461 };
3462
3463
3464 /**
3465 * CSI Ps C
3466 * Cursor Forward Ps Times (default = 1) (CUF).
3467 */
3468 Terminal.prototype.cursorForward = function(params) {
3469 var param = params[0];
3470 if (param < 1) param = 1;
3471 this.x += param;
3472 if (this.x >= this.cols) {
3473 this.x = this.cols - 1;
3474 }
3475 };
3476
3477
3478 /**
3479 * CSI Ps D
3480 * Cursor Backward Ps Times (default = 1) (CUB).
3481 */
3482 Terminal.prototype.cursorBackward = function(params) {
3483 var param = params[0];
3484 if (param < 1) param = 1;
3485 this.x -= param;
3486 if (this.x < 0) this.x = 0;
3487 };
3488
3489
3490 /**
3491 * CSI Ps ; Ps H
3492 * Cursor Position [row;column] (default = [1,1]) (CUP).
3493 */
3494 Terminal.prototype.cursorPos = function(params) {
3495 var row, col;
3496
3497 row = params[0] - 1;
3498
3499 if (params.length >= 2) {
3500 col = params[1] - 1;
3501 } else {
3502 col = 0;
3503 }
3504
3505 if (row < 0) {
3506 row = 0;
3507 } else if (row >= this.rows) {
3508 row = this.rows - 1;
3509 }
3510
3511 if (col < 0) {
3512 col = 0;
3513 } else if (col >= this.cols) {
3514 col = this.cols - 1;
3515 }
3516
3517 this.x = col;
3518 this.y = row;
3519 };
3520
3521
3522 /**
3523 * CSI Ps J Erase in Display (ED).
3524 * Ps = 0 -> Erase Below (default).
3525 * Ps = 1 -> Erase Above.
3526 * Ps = 2 -> Erase All.
3527 * Ps = 3 -> Erase Saved Lines (xterm).
3528 * CSI ? Ps J
3529 * Erase in Display (DECSED).
3530 * Ps = 0 -> Selective Erase Below (default).
3531 * Ps = 1 -> Selective Erase Above.
3532 * Ps = 2 -> Selective Erase All.
3533 */
3534 Terminal.prototype.eraseInDisplay = function(params) {
3535 var j;
3536 switch (params[0]) {
3537 case 0:
3538 this.eraseRight(this.x, this.y);
3539 j = this.y + 1;
3540 for (; j < this.rows; j++) {
3541 this.eraseLine(j);
3542 }
3543 break;
3544 case 1:
3545 this.eraseLeft(this.x, this.y);
3546 j = this.y;
3547 while (j--) {
3548 this.eraseLine(j);
3549 }
3550 break;
3551 case 2:
3552 j = this.rows;
3553 while (j--) this.eraseLine(j);
3554 break;
3555 case 3:
3556 ; // no saved lines
3557 break;
3558 }
3559 };
3560
3561
3562 /**
3563 * CSI Ps K Erase in Line (EL).
3564 * Ps = 0 -> Erase to Right (default).
3565 * Ps = 1 -> Erase to Left.
3566 * Ps = 2 -> Erase All.
3567 * CSI ? Ps K
3568 * Erase in Line (DECSEL).
3569 * Ps = 0 -> Selective Erase to Right (default).
3570 * Ps = 1 -> Selective Erase to Left.
3571 * Ps = 2 -> Selective Erase All.
3572 */
3573 Terminal.prototype.eraseInLine = function(params) {
3574 switch (params[0]) {
3575 case 0:
3576 this.eraseRight(this.x, this.y);
3577 break;
3578 case 1:
3579 this.eraseLeft(this.x, this.y);
3580 break;
3581 case 2:
3582 this.eraseLine(this.y);
3583 break;
3584 }
3585 };
3586
3587
3588 /**
3589 * CSI Pm m Character Attributes (SGR).
3590 * Ps = 0 -> Normal (default).
3591 * Ps = 1 -> Bold.
3592 * Ps = 4 -> Underlined.
3593 * Ps = 5 -> Blink (appears as Bold).
3594 * Ps = 7 -> Inverse.
3595 * Ps = 8 -> Invisible, i.e., hidden (VT300).
3596 * Ps = 2 2 -> Normal (neither bold nor faint).
3597 * Ps = 2 4 -> Not underlined.
3598 * Ps = 2 5 -> Steady (not blinking).
3599 * Ps = 2 7 -> Positive (not inverse).
3600 * Ps = 2 8 -> Visible, i.e., not hidden (VT300).
3601 * Ps = 3 0 -> Set foreground color to Black.
3602 * Ps = 3 1 -> Set foreground color to Red.
3603 * Ps = 3 2 -> Set foreground color to Green.
3604 * Ps = 3 3 -> Set foreground color to Yellow.
3605 * Ps = 3 4 -> Set foreground color to Blue.
3606 * Ps = 3 5 -> Set foreground color to Magenta.
3607 * Ps = 3 6 -> Set foreground color to Cyan.
3608 * Ps = 3 7 -> Set foreground color to White.
3609 * Ps = 3 9 -> Set foreground color to default (original).
3610 * Ps = 4 0 -> Set background color to Black.
3611 * Ps = 4 1 -> Set background color to Red.
3612 * Ps = 4 2 -> Set background color to Green.
3613 * Ps = 4 3 -> Set background color to Yellow.
3614 * Ps = 4 4 -> Set background color to Blue.
3615 * Ps = 4 5 -> Set background color to Magenta.
3616 * Ps = 4 6 -> Set background color to Cyan.
3617 * Ps = 4 7 -> Set background color to White.
3618 * Ps = 4 9 -> Set background color to default (original).
3619 *
3620 * If 16-color support is compiled, the following apply. Assume
3621 * that xterm's resources are set so that the ISO color codes are
3622 * the first 8 of a set of 16. Then the aixterm colors are the
3623 * bright versions of the ISO colors:
3624 * Ps = 9 0 -> Set foreground color to Black.
3625 * Ps = 9 1 -> Set foreground color to Red.
3626 * Ps = 9 2 -> Set foreground color to Green.
3627 * Ps = 9 3 -> Set foreground color to Yellow.
3628 * Ps = 9 4 -> Set foreground color to Blue.
3629 * Ps = 9 5 -> Set foreground color to Magenta.
3630 * Ps = 9 6 -> Set foreground color to Cyan.
3631 * Ps = 9 7 -> Set foreground color to White.
3632 * Ps = 1 0 0 -> Set background color to Black.
3633 * Ps = 1 0 1 -> Set background color to Red.
3634 * Ps = 1 0 2 -> Set background color to Green.
3635 * Ps = 1 0 3 -> Set background color to Yellow.
3636 * Ps = 1 0 4 -> Set background color to Blue.
3637 * Ps = 1 0 5 -> Set background color to Magenta.
3638 * Ps = 1 0 6 -> Set background color to Cyan.
3639 * Ps = 1 0 7 -> Set background color to White.
3640 *
3641 * If xterm is compiled with the 16-color support disabled, it
3642 * supports the following, from rxvt:
3643 * Ps = 1 0 0 -> Set foreground and background color to
3644 * default.
3645 *
3646 * If 88- or 256-color support is compiled, the following apply.
3647 * Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second
3648 * Ps.
3649 * Ps = 4 8 ; 5 ; Ps -> Set background color to the second
3650 * Ps.
3651 */
3652 Terminal.prototype.charAttributes = function(params) {
3653 // Optimize a single SGR0.
3654 if (params.length === 1 && params[0] === 0) {
3655 this.curAttr = this.defAttr;
3656 return;
3657 }
3658
3659 var l = params.length
3660 , i = 0
3661 , flags = this.curAttr >> 18
3662 , fg = (this.curAttr >> 9) & 0x1ff
3663 , bg = this.curAttr & 0x1ff
3664 , p;
3665
3666 for (; i < l; i++) {
3667 p = params[i];
3668 if (p >= 30 && p <= 37) {
3669 // fg color 8
3670 fg = p - 30;
3671 } else if (p >= 40 && p <= 47) {
3672 // bg color 8
3673 bg = p - 40;
3674 } else if (p >= 90 && p <= 97) {
3675 // fg color 16
3676 p += 8;
3677 fg = p - 90;
3678 } else if (p >= 100 && p <= 107) {
3679 // bg color 16
3680 p += 8;
3681 bg = p - 100;
3682 } else if (p === 0) {
3683 // default
3684 flags = this.defAttr >> 18;
3685 fg = (this.defAttr >> 9) & 0x1ff;
3686 bg = this.defAttr & 0x1ff;
3687 // flags = 0;
3688 // fg = 0x1ff;
3689 // bg = 0x1ff;
3690 } else if (p === 1) {
3691 // bold text
3692 flags |= 1;
3693 } else if (p === 4) {
3694 // underlined text
3695 flags |= 2;
3696 } else if (p === 5) {
3697 // blink
3698 flags |= 4;
3699 } else if (p === 7) {
3700 // inverse and positive
3701 // test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m'
3702 flags |= 8;
3703 } else if (p === 8) {
3704 // invisible
3705 flags |= 16;
3706 } else if (p === 22) {
3707 // not bold
3708 flags &= ~1;
3709 } else if (p === 24) {
3710 // not underlined
3711 flags &= ~2;
3712 } else if (p === 25) {
3713 // not blink
3714 flags &= ~4;
3715 } else if (p === 27) {
3716 // not inverse
3717 flags &= ~8;
3718 } else if (p === 28) {
3719 // not invisible
3720 flags &= ~16;
3721 } else if (p === 39) {
3722 // reset fg
3723 fg = (this.defAttr >> 9) & 0x1ff;
3724 } else if (p === 49) {
3725 // reset bg
3726 bg = this.defAttr & 0x1ff;
3727 } else if (p === 38) {
3728 // fg color 256
3729 if (params[i + 1] === 2) {
3730 i += 2;
3731 fg = matchColor(
3732 params[i] & 0xff,
3733 params[i + 1] & 0xff,
3734 params[i + 2] & 0xff);
3735 if (fg === -1) fg = 0x1ff;
3736 i += 2;
3737 } else if (params[i + 1] === 5) {
3738 i += 2;
3739 p = params[i] & 0xff;
3740 fg = p;
3741 }
3742 } else if (p === 48) {
3743 // bg color 256
3744 if (params[i + 1] === 2) {
3745 i += 2;
3746 bg = matchColor(
3747 params[i] & 0xff,
3748 params[i + 1] & 0xff,
3749 params[i + 2] & 0xff);
3750 if (bg === -1) bg = 0x1ff;
3751 i += 2;
3752 } else if (params[i + 1] === 5) {
3753 i += 2;
3754 p = params[i] & 0xff;
3755 bg = p;
3756 }
3757 } else if (p === 100) {
3758 // reset fg/bg
3759 fg = (this.defAttr >> 9) & 0x1ff;
3760 bg = this.defAttr & 0x1ff;
3761 } else {
3762 this.error('Unknown SGR attribute: %d.', p);
3763 }
3764 }
3765
3766 this.curAttr = (flags << 18) | (fg << 9) | bg;
3767 };
3768
3769
3770 /**
3771 * CSI Ps n Device Status Report (DSR).
3772 * Ps = 5 -> Status Report. Result (``OK'') is
3773 * CSI 0 n
3774 * Ps = 6 -> Report Cursor Position (CPR) [row;column].
3775 * Result is
3776 * CSI r ; c R
3777 * CSI ? Ps n
3778 * Device Status Report (DSR, DEC-specific).
3779 * Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI
3780 * ? r ; c R (assumes page is zero).
3781 * Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready).
3782 * or CSI ? 1 1 n (not ready).
3783 * Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked)
3784 * or CSI ? 2 1 n (locked).
3785 * Ps = 2 6 -> Report Keyboard status as
3786 * CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).
3787 * The last two parameters apply to VT400 & up, and denote key-
3788 * board ready and LK01 respectively.
3789 * Ps = 5 3 -> Report Locator status as
3790 * CSI ? 5 3 n Locator available, if compiled-in, or
3791 * CSI ? 5 0 n No Locator, if not.
3792 */
3793 Terminal.prototype.deviceStatus = function(params) {
3794 if (!this.prefix) {
3795 switch (params[0]) {
3796 case 5:
3797 // status report
3798 this.send('\x1b[0n');
3799 break;
3800 case 6:
3801 // cursor position
3802 this.send('\x1b['
3803 + (this.y + 1)
3804 + ';'
3805 + (this.x + 1)
3806 + 'R');
3807 break;
3808 }
3809 } else if (this.prefix === '?') {
3810 // modern xterm doesnt seem to
3811 // respond to any of these except ?6, 6, and 5
3812 switch (params[0]) {
3813 case 6:
3814 // cursor position
3815 this.send('\x1b[?'
3816 + (this.y + 1)
3817 + ';'
3818 + (this.x + 1)
3819 + 'R');
3820 break;
3821 case 15:
3822 // no printer
3823 // this.send('\x1b[?11n');
3824 break;
3825 case 25:
3826 // dont support user defined keys
3827 // this.send('\x1b[?21n');
3828 break;
3829 case 26:
3830 // north american keyboard
3831 // this.send('\x1b[?27;1;0;0n');
3832 break;
3833 case 53:
3834 // no dec locator/mouse
3835 // this.send('\x1b[?50n');
3836 break;
3837 }
3838 }
3839 };
3840
3841
3842 /**
3843 * Additions
3844 */
3845
3846 /**
3847 * CSI Ps @
3848 * Insert Ps (Blank) Character(s) (default = 1) (ICH).
3849 */
3850 Terminal.prototype.insertChars = function(params) {
3851 var param, row, j, ch;
3852
3853 param = params[0];
3854 if (param < 1) param = 1;
3855
3856 row = this.y + this.ybase;
3857 j = this.x;
3858 ch = [this.eraseAttr(), ' ', 1]; // xterm
3859
3860 while (param-- && j < this.cols) {
3861 this.lines[row].splice(j++, 0, ch);
3862 this.lines[row].pop();
3863 }
3864 };
3865
3866 /**
3867 * CSI Ps E
3868 * Cursor Next Line Ps Times (default = 1) (CNL).
3869 * same as CSI Ps B ?
3870 */
3871 Terminal.prototype.cursorNextLine = function(params) {
3872 var param = params[0];
3873 if (param < 1) param = 1;
3874 this.y += param;
3875 if (this.y >= this.rows) {
3876 this.y = this.rows - 1;
3877 }
3878 this.x = 0;
3879 };
3880
3881
3882 /**
3883 * CSI Ps F
3884 * Cursor Preceding Line Ps Times (default = 1) (CNL).
3885 * reuse CSI Ps A ?
3886 */
3887 Terminal.prototype.cursorPrecedingLine = function(params) {
3888 var param = params[0];
3889 if (param < 1) param = 1;
3890 this.y -= param;
3891 if (this.y < 0) this.y = 0;
3892 this.x = 0;
3893 };
3894
3895
3896 /**
3897 * CSI Ps G
3898 * Cursor Character Absolute [column] (default = [row,1]) (CHA).
3899 */
3900 Terminal.prototype.cursorCharAbsolute = function(params) {
3901 var param = params[0];
3902 if (param < 1) param = 1;
3903 this.x = param - 1;
3904 };
3905
3906
3907 /**
3908 * CSI Ps L
3909 * Insert Ps Line(s) (default = 1) (IL).
3910 */
3911 Terminal.prototype.insertLines = function(params) {
3912 var param, row, j;
3913
3914 param = params[0];
3915 if (param < 1) param = 1;
3916 row = this.y + this.ybase;
3917
3918 j = this.rows - 1 - this.scrollBottom;
3919 j = this.rows - 1 + this.ybase - j + 1;
3920
3921 while (param--) {
3922 // test: echo -e '\e[44m\e[1L\e[0m'
3923 // blankLine(true) - xterm/linux behavior
3924 this.lines.splice(row, 0, this.blankLine(true));
3925 this.lines.splice(j, 1);
3926 }
3927
3928 // this.maxRange();
3929 this.updateRange(this.y);
3930 this.updateRange(this.scrollBottom);
3931 };
3932
3933
3934 /**
3935 * CSI Ps M
3936 * Delete Ps Line(s) (default = 1) (DL).
3937 */
3938 Terminal.prototype.deleteLines = function(params) {
3939 var param, row, j;
3940
3941 param = params[0];
3942 if (param < 1) param = 1;
3943 row = this.y + this.ybase;
3944
3945 j = this.rows - 1 - this.scrollBottom;
3946 j = this.rows - 1 + this.ybase - j;
3947
3948 while (param--) {
3949 // test: echo -e '\e[44m\e[1M\e[0m'
3950 // blankLine(true) - xterm/linux behavior
3951 this.lines.splice(j + 1, 0, this.blankLine(true));
3952 this.lines.splice(row, 1);
3953 }
3954
3955 // this.maxRange();
3956 this.updateRange(this.y);
3957 this.updateRange(this.scrollBottom);
3958 };
3959
3960
3961 /**
3962 * CSI Ps P
3963 * Delete Ps Character(s) (default = 1) (DCH).
3964 */
3965 Terminal.prototype.deleteChars = function(params) {
3966 var param, row, ch;
3967
3968 param = params[0];
3969 if (param < 1) param = 1;
3970
3971 row = this.y + this.ybase;
3972 ch = [this.eraseAttr(), ' ', 1]; // xterm
3973
3974 while (param--) {
3975 this.lines[row].splice(this.x, 1);
3976 this.lines[row].push(ch);
3977 }
3978 };
3979
3980 /**
3981 * CSI Ps X
3982 * Erase Ps Character(s) (default = 1) (ECH).
3983 */
3984 Terminal.prototype.eraseChars = function(params) {
3985 var param, row, j, ch;
3986
3987 param = params[0];
3988 if (param < 1) param = 1;
3989
3990 row = this.y + this.ybase;
3991 j = this.x;
3992 ch = [this.eraseAttr(), ' ', 1]; // xterm
3993
3994 while (param-- && j < this.cols) {
3995 this.lines[row][j++] = ch;
3996 }
3997 };
3998
3999 /**
4000 * CSI Pm ` Character Position Absolute
4001 * [column] (default = [row,1]) (HPA).
4002 */
4003 Terminal.prototype.charPosAbsolute = function(params) {
4004 var param = params[0];
4005 if (param < 1) param = 1;
4006 this.x = param - 1;
4007 if (this.x >= this.cols) {
4008 this.x = this.cols - 1;
4009 }
4010 };
4011
4012
4013 /**
4014 * 141 61 a * HPR -
4015 * Horizontal Position Relative
4016 * reuse CSI Ps C ?
4017 */
4018 Terminal.prototype.HPositionRelative = function(params) {
4019 var param = params[0];
4020 if (param < 1) param = 1;
4021 this.x += param;
4022 if (this.x >= this.cols) {
4023 this.x = this.cols - 1;
4024 }
4025 };
4026
4027
4028 /**
4029 * CSI Ps c Send Device Attributes (Primary DA).
4030 * Ps = 0 or omitted -> request attributes from terminal. The
4031 * response depends on the decTerminalID resource setting.
4032 * -> CSI ? 1 ; 2 c (``VT100 with Advanced Video Option'')
4033 * -> CSI ? 1 ; 0 c (``VT101 with No Options'')
4034 * -> CSI ? 6 c (``VT102'')
4035 * -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c (``VT220'')
4036 * The VT100-style response parameters do not mean anything by
4037 * themselves. VT220 parameters do, telling the host what fea-
4038 * tures the terminal supports:
4039 * Ps = 1 -> 132-columns.
4040 * Ps = 2 -> Printer.
4041 * Ps = 6 -> Selective erase.
4042 * Ps = 8 -> User-defined keys.
4043 * Ps = 9 -> National replacement character sets.
4044 * Ps = 1 5 -> Technical characters.
4045 * Ps = 2 2 -> ANSI color, e.g., VT525.
4046 * Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode).
4047 * CSI > Ps c
4048 * Send Device Attributes (Secondary DA).
4049 * Ps = 0 or omitted -> request the terminal's identification
4050 * code. The response depends on the decTerminalID resource set-
4051 * ting. It should apply only to VT220 and up, but xterm extends
4052 * this to VT100.
4053 * -> CSI > Pp ; Pv ; Pc c
4054 * where Pp denotes the terminal type
4055 * Pp = 0 -> ``VT100''.
4056 * Pp = 1 -> ``VT220''.
4057 * and Pv is the firmware version (for xterm, this was originally
4058 * the XFree86 patch number, starting with 95). In a DEC termi-
4059 * nal, Pc indicates the ROM cartridge registration number and is
4060 * always zero.
4061 * More information:
4062 * xterm/charproc.c - line 2012, for more information.
4063 * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?)
4064 */
4065 Terminal.prototype.sendDeviceAttributes = function(params) {
4066 if (params[0] > 0) return;
4067
4068 if (!this.prefix) {
4069 if (this.is('xterm')
4070 || this.is('rxvt-unicode')
4071 || this.is('screen')) {
4072 this.send('\x1b[?1;2c');
4073 } else if (this.is('linux')) {
4074 this.send('\x1b[?6c');
4075 }
4076 } else if (this.prefix === '>') {
4077 // xterm and urxvt
4078 // seem to spit this
4079 // out around ~370 times (?).
4080 if (this.is('xterm')) {
4081 this.send('\x1b[>0;276;0c');
4082 } else if (this.is('rxvt-unicode')) {
4083 this.send('\x1b[>85;95;0c');
4084 } else if (this.is('linux')) {
4085 // not supported by linux console.
4086 // linux console echoes parameters.
4087 this.send(params[0] + 'c');
4088 } else if (this.is('screen')) {
4089 this.send('\x1b[>83;40003;0c');
4090 }
4091 }
4092 };
4093
4094
4095 /**
4096 * CSI Pm d
4097 * Line Position Absolute [row] (default = [1,column]) (VPA).
4098 */
4099 Terminal.prototype.linePosAbsolute = function(params) {
4100 var param = params[0];
4101 if (param < 1) param = 1;
4102 this.y = param - 1;
4103 if (this.y >= this.rows) {
4104 this.y = this.rows - 1;
4105 }
4106 };
4107
4108
4109 /**
4110 * 145 65 e * VPR - Vertical Position Relative
4111 * reuse CSI Ps B ?
4112 */
4113 Terminal.prototype.VPositionRelative = function(params) {
4114 var param = params[0];
4115 if (param < 1) param = 1;
4116 this.y += param;
4117 if (this.y >= this.rows) {
4118 this.y = this.rows - 1;
4119 }
4120 };
4121
4122
4123 /**
4124 * CSI Ps ; Ps f
4125 * Horizontal and Vertical Position [row;column] (default =
4126 * [1,1]) (HVP).
4127 */
4128 Terminal.prototype.HVPosition = function(params) {
4129 if (params[0] < 1) params[0] = 1;
4130 if (params[1] < 1) params[1] = 1;
4131
4132 this.y = params[0] - 1;
4133 if (this.y >= this.rows) {
4134 this.y = this.rows - 1;
4135 }
4136
4137 this.x = params[1] - 1;
4138 if (this.x >= this.cols) {
4139 this.x = this.cols - 1;
4140 }
4141 };
4142
4143
4144 /**
4145 * CSI Pm h Set Mode (SM).
4146 * Ps = 2 -> Keyboard Action Mode (AM).
4147 * Ps = 4 -> Insert Mode (IRM).
4148 * Ps = 1 2 -> Send/receive (SRM).
4149 * Ps = 2 0 -> Automatic Newline (LNM).
4150 * CSI ? Pm h
4151 * DEC Private Mode Set (DECSET).
4152 * Ps = 1 -> Application Cursor Keys (DECCKM).
4153 * Ps = 2 -> Designate USASCII for character sets G0-G3
4154 * (DECANM), and set VT100 mode.
4155 * Ps = 3 -> 132 Column Mode (DECCOLM).
4156 * Ps = 4 -> Smooth (Slow) Scroll (DECSCLM).
4157 * Ps = 5 -> Reverse Video (DECSCNM).
4158 * Ps = 6 -> Origin Mode (DECOM).
4159 * Ps = 7 -> Wraparound Mode (DECAWM).
4160 * Ps = 8 -> Auto-repeat Keys (DECARM).
4161 * Ps = 9 -> Send Mouse X & Y on button press. See the sec-
4162 * tion Mouse Tracking.
4163 * Ps = 1 0 -> Show toolbar (rxvt).
4164 * Ps = 1 2 -> Start Blinking Cursor (att610).
4165 * Ps = 1 8 -> Print form feed (DECPFF).
4166 * Ps = 1 9 -> Set print extent to full screen (DECPEX).
4167 * Ps = 2 5 -> Show Cursor (DECTCEM).
4168 * Ps = 3 0 -> Show scrollbar (rxvt).
4169 * Ps = 3 5 -> Enable font-shifting functions (rxvt).
4170 * Ps = 3 8 -> Enter Tektronix Mode (DECTEK).
4171 * Ps = 4 0 -> Allow 80 -> 132 Mode.
4172 * Ps = 4 1 -> more(1) fix (see curses resource).
4173 * Ps = 4 2 -> Enable Nation Replacement Character sets (DECN-
4174 * RCM).
4175 * Ps = 4 4 -> Turn On Margin Bell.
4176 * Ps = 4 5 -> Reverse-wraparound Mode.
4177 * Ps = 4 6 -> Start Logging. This is normally disabled by a
4178 * compile-time option.
4179 * Ps = 4 7 -> Use Alternate Screen Buffer. (This may be dis-
4180 * abled by the titeInhibit resource).
4181 * Ps = 6 6 -> Application keypad (DECNKM).
4182 * Ps = 6 7 -> Backarrow key sends backspace (DECBKM).
4183 * Ps = 1 0 0 0 -> Send Mouse X & Y on button press and
4184 * release. See the section Mouse Tracking.
4185 * Ps = 1 0 0 1 -> Use Hilite Mouse Tracking.
4186 * Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking.
4187 * Ps = 1 0 0 3 -> Use All Motion Mouse Tracking.
4188 * Ps = 1 0 0 4 -> Send FocusIn/FocusOut events.
4189 * Ps = 1 0 0 5 -> Enable Extended Mouse Mode.
4190 * Ps = 1 0 1 0 -> Scroll to bottom on tty output (rxvt).
4191 * Ps = 1 0 1 1 -> Scroll to bottom on key press (rxvt).
4192 * Ps = 1 0 3 4 -> Interpret "meta" key, sets eighth bit.
4193 * (enables the eightBitInput resource).
4194 * Ps = 1 0 3 5 -> Enable special modifiers for Alt and Num-
4195 * Lock keys. (This enables the numLock resource).
4196 * Ps = 1 0 3 6 -> Send ESC when Meta modifies a key. (This
4197 * enables the metaSendsEscape resource).
4198 * Ps = 1 0 3 7 -> Send DEL from the editing-keypad Delete
4199 * key.
4200 * Ps = 1 0 3 9 -> Send ESC when Alt modifies a key. (This
4201 * enables the altSendsEscape resource).
4202 * Ps = 1 0 4 0 -> Keep selection even if not highlighted.
4203 * (This enables the keepSelection resource).
4204 * Ps = 1 0 4 1 -> Use the CLIPBOARD selection. (This enables
4205 * the selectToClipboard resource).
4206 * Ps = 1 0 4 2 -> Enable Urgency window manager hint when
4207 * Control-G is received. (This enables the bellIsUrgent
4208 * resource).
4209 * Ps = 1 0 4 3 -> Enable raising of the window when Control-G
4210 * is received. (enables the popOnBell resource).
4211 * Ps = 1 0 4 7 -> Use Alternate Screen Buffer. (This may be
4212 * disabled by the titeInhibit resource).
4213 * Ps = 1 0 4 8 -> Save cursor as in DECSC. (This may be dis-
4214 * abled by the titeInhibit resource).
4215 * Ps = 1 0 4 9 -> Save cursor as in DECSC and use Alternate
4216 * Screen Buffer, clearing it first. (This may be disabled by
4217 * the titeInhibit resource). This combines the effects of the 1
4218 * 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based
4219 * applications rather than the 4 7 mode.
4220 * Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode.
4221 * Ps = 1 0 5 1 -> Set Sun function-key mode.
4222 * Ps = 1 0 5 2 -> Set HP function-key mode.
4223 * Ps = 1 0 5 3 -> Set SCO function-key mode.
4224 * Ps = 1 0 6 0 -> Set legacy keyboard emulation (X11R6).
4225 * Ps = 1 0 6 1 -> Set VT220 keyboard emulation.
4226 * Ps = 2 0 0 4 -> Set bracketed paste mode.
4227 * Modes:
4228 * http: *vt100.net/docs/vt220-rm/chapter4.html
4229 */
4230 Terminal.prototype.setMode = function(params) {
4231 if (typeof params === 'object') {
4232 var l = params.length
4233 , i = 0;
4234
4235 for (; i < l; i++) {
4236 this.setMode(params[i]);
4237 }
4238
4239 return;
4240 }
4241
4242 if (!this.prefix) {
4243 switch (params) {
4244 case 4:
4245 this.insertMode = true;
4246 break;
4247 case 20:
4248 //this.convertEol = true;
4249 break;
4250 }
4251 } else if (this.prefix === '?') {
4252 switch (params) {
4253 case 1:
4254 this.applicationCursor = true;
4255 break;
4256 case 2:
4257 this.setgCharset(0, Terminal.charsets.US);
4258 this.setgCharset(1, Terminal.charsets.US);
4259 this.setgCharset(2, Terminal.charsets.US);
4260 this.setgCharset(3, Terminal.charsets.US);
4261 // set VT100 mode here
4262 break;
4263 case 3: // 132 col mode
4264 this.savedCols = this.cols;
4265 this.resize(132, this.rows);
4266 break;
4267 case 6:
4268 this.originMode = true;
4269 break;
4270 case 7:
4271 this.wraparoundMode = true;
4272 break;
4273 case 12:
4274 // this.cursorBlink = true;
4275 break;
4276 case 66:
4277 this.log('Serial port requested application keypad.');
4278 this.applicationKeypad = true;
4279 this.viewport.setApplicationMode(true);
4280 break;
4281 case 9: // X10 Mouse
4282 // no release, no motion, no wheel, no modifiers.
4283 case 1000: // vt200 mouse
4284 // no motion.
4285 // no modifiers, except control on the wheel.
4286 case 1002: // button event mouse
4287 case 1003: // any event mouse
4288 // any event - sends motion events,
4289 // even if there is no button held down.
4290 this.x10Mouse = params === 9;
4291 this.vt200Mouse = params === 1000;
4292 this.normalMouse = params > 1000;
4293 this.mouseEvents = true;
4294 this.element.style.cursor = 'default';
4295 this.log('Binding to mouse events.');
4296 break;
4297 case 1004: // send focusin/focusout events
4298 // focusin: ^[[I
4299 // focusout: ^[[O
4300 this.sendFocus = true;
4301 break;
4302 case 1005: // utf8 ext mode mouse
4303 this.utfMouse = true;
4304 // for wide terminals
4305 // simply encodes large values as utf8 characters
4306 break;
4307 case 1006: // sgr ext mode mouse
4308 this.sgrMouse = true;
4309 // for wide terminals
4310 // does not add 32 to fields
4311 // press: ^[[<b;x;yM
4312 // release: ^[[<b;x;ym
4313 break;
4314 case 1015: // urxvt ext mode mouse
4315 this.urxvtMouse = true;
4316 // for wide terminals
4317 // numbers for fields
4318 // press: ^[[b;x;yM
4319 // motion: ^[[b;x;yT
4320 break;
4321 case 25: // show cursor
4322 this.cursorHidden = false;
4323 break;
4324 case 1049: // alt screen buffer cursor
4325 //this.saveCursor();
4326 ; // FALL-THROUGH
4327 case 47: // alt screen buffer
4328 case 1047: // alt screen buffer
4329 if (!this.normal) {
4330 var normal = {
4331 lines: this.lines,
4332 ybase: this.ybase,
4333 ydisp: this.ydisp,
4334 x: this.x,
4335 y: this.y,
4336 scrollTop: this.scrollTop,
4337 scrollBottom: this.scrollBottom,
4338 tabs: this.tabs
4339 // XXX save charset(s) here?
4340 // charset: this.charset,
4341 // glevel: this.glevel,
4342 // charsets: this.charsets
4343 };
4344 this.reset();
4345 this.normal = normal;
4346 this.showCursor();
4347 }
4348 break;
4349 }
4350 }
4351 };
4352
4353 /**
4354 * CSI Pm l Reset Mode (RM).
4355 * Ps = 2 -> Keyboard Action Mode (AM).
4356 * Ps = 4 -> Replace Mode (IRM).
4357 * Ps = 1 2 -> Send/receive (SRM).
4358 * Ps = 2 0 -> Normal Linefeed (LNM).
4359 * CSI ? Pm l
4360 * DEC Private Mode Reset (DECRST).
4361 * Ps = 1 -> Normal Cursor Keys (DECCKM).
4362 * Ps = 2 -> Designate VT52 mode (DECANM).
4363 * Ps = 3 -> 80 Column Mode (DECCOLM).
4364 * Ps = 4 -> Jump (Fast) Scroll (DECSCLM).
4365 * Ps = 5 -> Normal Video (DECSCNM).
4366 * Ps = 6 -> Normal Cursor Mode (DECOM).
4367 * Ps = 7 -> No Wraparound Mode (DECAWM).
4368 * Ps = 8 -> No Auto-repeat Keys (DECARM).
4369 * Ps = 9 -> Don't send Mouse X & Y on button press.
4370 * Ps = 1 0 -> Hide toolbar (rxvt).
4371 * Ps = 1 2 -> Stop Blinking Cursor (att610).
4372 * Ps = 1 8 -> Don't print form feed (DECPFF).
4373 * Ps = 1 9 -> Limit print to scrolling region (DECPEX).
4374 * Ps = 2 5 -> Hide Cursor (DECTCEM).
4375 * Ps = 3 0 -> Don't show scrollbar (rxvt).
4376 * Ps = 3 5 -> Disable font-shifting functions (rxvt).
4377 * Ps = 4 0 -> Disallow 80 -> 132 Mode.
4378 * Ps = 4 1 -> No more(1) fix (see curses resource).
4379 * Ps = 4 2 -> Disable Nation Replacement Character sets (DEC-
4380 * NRCM).
4381 * Ps = 4 4 -> Turn Off Margin Bell.
4382 * Ps = 4 5 -> No Reverse-wraparound Mode.
4383 * Ps = 4 6 -> Stop Logging. (This is normally disabled by a
4384 * compile-time option).
4385 * Ps = 4 7 -> Use Normal Screen Buffer.
4386 * Ps = 6 6 -> Numeric keypad (DECNKM).
4387 * Ps = 6 7 -> Backarrow key sends delete (DECBKM).
4388 * Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and
4389 * release. See the section Mouse Tracking.
4390 * Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking.
4391 * Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking.
4392 * Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking.
4393 * Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events.
4394 * Ps = 1 0 0 5 -> Disable Extended Mouse Mode.
4395 * Ps = 1 0 1 0 -> Don't scroll to bottom on tty output
4396 * (rxvt).
4397 * Ps = 1 0 1 1 -> Don't scroll to bottom on key press (rxvt).
4398 * Ps = 1 0 3 4 -> Don't interpret "meta" key. (This disables
4399 * the eightBitInput resource).
4400 * Ps = 1 0 3 5 -> Disable special modifiers for Alt and Num-
4401 * Lock keys. (This disables the numLock resource).
4402 * Ps = 1 0 3 6 -> Don't send ESC when Meta modifies a key.
4403 * (This disables the metaSendsEscape resource).
4404 * Ps = 1 0 3 7 -> Send VT220 Remove from the editing-keypad
4405 * Delete key.
4406 * Ps = 1 0 3 9 -> Don't send ESC when Alt modifies a key.
4407 * (This disables the altSendsEscape resource).
4408 * Ps = 1 0 4 0 -> Do not keep selection when not highlighted.
4409 * (This disables the keepSelection resource).
4410 * Ps = 1 0 4 1 -> Use the PRIMARY selection. (This disables
4411 * the selectToClipboard resource).
4412 * Ps = 1 0 4 2 -> Disable Urgency window manager hint when
4413 * Control-G is received. (This disables the bellIsUrgent
4414 * resource).
4415 * Ps = 1 0 4 3 -> Disable raising of the window when Control-
4416 * G is received. (This disables the popOnBell resource).
4417 * Ps = 1 0 4 7 -> Use Normal Screen Buffer, clearing screen
4418 * first if in the Alternate Screen. (This may be disabled by
4419 * the titeInhibit resource).
4420 * Ps = 1 0 4 8 -> Restore cursor as in DECRC. (This may be
4421 * disabled by the titeInhibit resource).
4422 * Ps = 1 0 4 9 -> Use Normal Screen Buffer and restore cursor
4423 * as in DECRC. (This may be disabled by the titeInhibit
4424 * resource). This combines the effects of the 1 0 4 7 and 1 0
4425 * 4 8 modes. Use this with terminfo-based applications rather
4426 * than the 4 7 mode.
4427 * Ps = 1 0 5 0 -> Reset terminfo/termcap function-key mode.
4428 * Ps = 1 0 5 1 -> Reset Sun function-key mode.
4429 * Ps = 1 0 5 2 -> Reset HP function-key mode.
4430 * Ps = 1 0 5 3 -> Reset SCO function-key mode.
4431 * Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6).
4432 * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style.
4433 * Ps = 2 0 0 4 -> Reset bracketed paste mode.
4434 */
4435 Terminal.prototype.resetMode = function(params) {
4436 if (typeof params === 'object') {
4437 var l = params.length
4438 , i = 0;
4439
4440 for (; i < l; i++) {
4441 this.resetMode(params[i]);
4442 }
4443
4444 return;
4445 }
4446
4447 if (!this.prefix) {
4448 switch (params) {
4449 case 4:
4450 this.insertMode = false;
4451 break;
4452 case 20:
4453 //this.convertEol = false;
4454 break;
4455 }
4456 } else if (this.prefix === '?') {
4457 switch (params) {
4458 case 1:
4459 this.applicationCursor = false;
4460 break;
4461 case 3:
4462 if (this.cols === 132 && this.savedCols) {
4463 this.resize(this.savedCols, this.rows);
4464 }
4465 delete this.savedCols;
4466 break;
4467 case 6:
4468 this.originMode = false;
4469 break;
4470 case 7:
4471 this.wraparoundMode = false;
4472 break;
4473 case 12:
4474 // this.cursorBlink = false;
4475 break;
4476 case 66:
4477 this.log('Switching back to normal keypad.');
4478 this.viewport.setApplicationMode(false);
4479 this.applicationKeypad = false;
4480 break;
4481 case 9: // X10 Mouse
4482 case 1000: // vt200 mouse
4483 case 1002: // button event mouse
4484 case 1003: // any event mouse
4485 this.x10Mouse = false;
4486 this.vt200Mouse = false;
4487 this.normalMouse = false;
4488 this.mouseEvents = false;
4489 this.element.style.cursor = '';
4490 break;
4491 case 1004: // send focusin/focusout events
4492 this.sendFocus = false;
4493 break;
4494 case 1005: // utf8 ext mode mouse
4495 this.utfMouse = false;
4496 break;
4497 case 1006: // sgr ext mode mouse
4498 this.sgrMouse = false;
4499 break;
4500 case 1015: // urxvt ext mode mouse
4501 this.urxvtMouse = false;
4502 break;
4503 case 25: // hide cursor
4504 this.cursorHidden = true;
4505 break;
4506 case 1049: // alt screen buffer cursor
4507 ; // FALL-THROUGH
4508 case 47: // normal screen buffer
4509 case 1047: // normal screen buffer - clearing it first
4510 if (this.normal) {
4511 this.lines = this.normal.lines;
4512 this.ybase = this.normal.ybase;
4513 this.ydisp = this.normal.ydisp;
4514 this.x = this.normal.x;
4515 this.y = this.normal.y;
4516 this.scrollTop = this.normal.scrollTop;
4517 this.scrollBottom = this.normal.scrollBottom;
4518 this.tabs = this.normal.tabs;
4519 this.normal = null;
4520 // if (params === 1049) {
4521 // this.x = this.savedX;
4522 // this.y = this.savedY;
4523 // }
4524 this.refresh(0, this.rows - 1);
4525 this.showCursor();
4526 }
4527 break;
4528 }
4529 }
4530 };
4531
4532
4533 /**
4534 * CSI Ps ; Ps r
4535 * Set Scrolling Region [top;bottom] (default = full size of win-
4536 * dow) (DECSTBM).
4537 * CSI ? Pm r
4538 */
4539 Terminal.prototype.setScrollRegion = function(params) {
4540 if (this.prefix) return;
4541 this.scrollTop = (params[0] || 1) - 1;
4542 this.scrollBottom = (params[1] || this.rows) - 1;
4543 this.x = 0;
4544 this.y = 0;
4545 };
4546
4547
4548 /**
4549 * CSI s
4550 * Save cursor (ANSI.SYS).
4551 */
4552 Terminal.prototype.saveCursor = function(params) {
4553 this.savedX = this.x;
4554 this.savedY = this.y;
4555 };
4556
4557
4558 /**
4559 * CSI u
4560 * Restore cursor (ANSI.SYS).
4561 */
4562 Terminal.prototype.restoreCursor = function(params) {
4563 this.x = this.savedX || 0;
4564 this.y = this.savedY || 0;
4565 };
4566
4567
4568 /**
4569 * Lesser Used
4570 */
4571
4572 /**
4573 * CSI Ps I
4574 * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).
4575 */
4576 Terminal.prototype.cursorForwardTab = function(params) {
4577 var param = params[0] || 1;
4578 while (param--) {
4579 this.x = this.nextStop();
4580 }
4581 };
4582
4583
4584 /**
4585 * CSI Ps S Scroll up Ps lines (default = 1) (SU).
4586 */
4587 Terminal.prototype.scrollUp = function(params) {
4588 var param = params[0] || 1;
4589 while (param--) {
4590 this.lines.splice(this.ybase + this.scrollTop, 1);
4591 this.lines.splice(this.ybase + this.scrollBottom, 0, this.blankLine());
4592 }
4593 // this.maxRange();
4594 this.updateRange(this.scrollTop);
4595 this.updateRange(this.scrollBottom);
4596 };
4597
4598
4599 /**
4600 * CSI Ps T Scroll down Ps lines (default = 1) (SD).
4601 */
4602 Terminal.prototype.scrollDown = function(params) {
4603 var param = params[0] || 1;
4604 while (param--) {
4605 this.lines.splice(this.ybase + this.scrollBottom, 1);
4606 this.lines.splice(this.ybase + this.scrollTop, 0, this.blankLine());
4607 }
4608 // this.maxRange();
4609 this.updateRange(this.scrollTop);
4610 this.updateRange(this.scrollBottom);
4611 };
4612
4613
4614 /**
4615 * CSI Ps ; Ps ; Ps ; Ps ; Ps T
4616 * Initiate highlight mouse tracking. Parameters are
4617 * [func;startx;starty;firstrow;lastrow]. See the section Mouse
4618 * Tracking.
4619 */
4620 Terminal.prototype.initMouseTracking = function(params) {
4621 // Relevant: DECSET 1001
4622 };
4623
4624
4625 /**
4626 * CSI > Ps; Ps T
4627 * Reset one or more features of the title modes to the default
4628 * value. Normally, "reset" disables the feature. It is possi-
4629 * ble to disable the ability to reset features by compiling a
4630 * different default for the title modes into xterm.
4631 * Ps = 0 -> Do not set window/icon labels using hexadecimal.
4632 * Ps = 1 -> Do not query window/icon labels using hexadeci-
4633 * mal.
4634 * Ps = 2 -> Do not set window/icon labels using UTF-8.
4635 * Ps = 3 -> Do not query window/icon labels using UTF-8.
4636 * (See discussion of "Title Modes").
4637 */
4638 Terminal.prototype.resetTitleModes = function(params) {
4639 ;
4640 };
4641
4642
4643 /**
4644 * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).
4645 */
4646 Terminal.prototype.cursorBackwardTab = function(params) {
4647 var param = params[0] || 1;
4648 while (param--) {
4649 this.x = this.prevStop();
4650 }
4651 };
4652
4653
4654 /**
4655 * CSI Ps b Repeat the preceding graphic character Ps times (REP).
4656 */
4657 Terminal.prototype.repeatPrecedingCharacter = function(params) {
4658 var param = params[0] || 1
4659 , line = this.lines[this.ybase + this.y]
4660 , ch = line[this.x - 1] || [this.defAttr, ' ', 1];
4661
4662 while (param--) line[this.x++] = ch;
4663 };
4664
4665
4666 /**
4667 * CSI Ps g Tab Clear (TBC).
4668 * Ps = 0 -> Clear Current Column (default).
4669 * Ps = 3 -> Clear All.
4670 * Potentially:
4671 * Ps = 2 -> Clear Stops on Line.
4672 * http://vt100.net/annarbor/aaa-ug/section6.html
4673 */
4674 Terminal.prototype.tabClear = function(params) {
4675 var param = params[0];
4676 if (param <= 0) {
4677 delete this.tabs[this.x];
4678 } else if (param === 3) {
4679 this.tabs = {};
4680 }
4681 };
4682
4683
4684 /**
4685 * CSI Pm i Media Copy (MC).
4686 * Ps = 0 -> Print screen (default).
4687 * Ps = 4 -> Turn off printer controller mode.
4688 * Ps = 5 -> Turn on printer controller mode.
4689 * CSI ? Pm i
4690 * Media Copy (MC, DEC-specific).
4691 * Ps = 1 -> Print line containing cursor.
4692 * Ps = 4 -> Turn off autoprint mode.
4693 * Ps = 5 -> Turn on autoprint mode.
4694 * Ps = 1 0 -> Print composed display, ignores DECPEX.
4695 * Ps = 1 1 -> Print all pages.
4696 */
4697 Terminal.prototype.mediaCopy = function(params) {
4698 ;
4699 };
4700
4701
4702 /**
4703 * CSI > Ps; Ps m
4704 * Set or reset resource-values used by xterm to decide whether
4705 * to construct escape sequences holding information about the
4706 * modifiers pressed with a given key. The first parameter iden-
4707 * tifies the resource to set/reset. The second parameter is the
4708 * value to assign to the resource. If the second parameter is
4709 * omitted, the resource is reset to its initial value.
4710 * Ps = 1 -> modifyCursorKeys.
4711 * Ps = 2 -> modifyFunctionKeys.
4712 * Ps = 4 -> modifyOtherKeys.
4713 * If no parameters are given, all resources are reset to their
4714 * initial values.
4715 */
4716 Terminal.prototype.setResources = function(params) {
4717 ;
4718 };
4719
4720
4721 /**
4722 * CSI > Ps n
4723 * Disable modifiers which may be enabled via the CSI > Ps; Ps m
4724 * sequence. This corresponds to a resource value of "-1", which
4725 * cannot be set with the other sequence. The parameter identi-
4726 * fies the resource to be disabled:
4727 * Ps = 1 -> modifyCursorKeys.
4728 * Ps = 2 -> modifyFunctionKeys.
4729 * Ps = 4 -> modifyOtherKeys.
4730 * If the parameter is omitted, modifyFunctionKeys is disabled.
4731 * When modifyFunctionKeys is disabled, xterm uses the modifier
4732 * keys to make an extended sequence of functions rather than
4733 * adding a parameter to each function key to denote the modi-
4734 * fiers.
4735 */
4736 Terminal.prototype.disableModifiers = function(params) {
4737 ;
4738 };
4739
4740
4741 /**
4742 * CSI > Ps p
4743 * Set resource value pointerMode. This is used by xterm to
4744 * decide whether to hide the pointer cursor as the user types.
4745 * Valid values for the parameter:
4746 * Ps = 0 -> never hide the pointer.
4747 * Ps = 1 -> hide if the mouse tracking mode is not enabled.
4748 * Ps = 2 -> always hide the pointer. If no parameter is
4749 * given, xterm uses the default, which is 1 .
4750 */
4751 Terminal.prototype.setPointerMode = function(params) {
4752 ;
4753 };
4754
4755
4756 /**
4757 * CSI ! p Soft terminal reset (DECSTR).
4758 * http://vt100.net/docs/vt220-rm/table4-10.html
4759 */
4760 Terminal.prototype.softReset = function(params) {
4761 this.cursorHidden = false;
4762 this.insertMode = false;
4763 this.originMode = false;
4764 this.wraparoundMode = false; // autowrap
4765 this.applicationKeypad = false; // ?
4766 this.applicationCursor = false;
4767 this.scrollTop = 0;
4768 this.scrollBottom = this.rows - 1;
4769 this.curAttr = this.defAttr;
4770 this.x = this.y = 0; // ?
4771 this.charset = null;
4772 this.glevel = 0; // ??
4773 this.charsets = [null]; // ??
4774 };
4775
4776
4777 /**
4778 * CSI Ps$ p
4779 * Request ANSI mode (DECRQM). For VT300 and up, reply is
4780 * CSI Ps; Pm$ y
4781 * where Ps is the mode number as in RM, and Pm is the mode
4782 * value:
4783 * 0 - not recognized
4784 * 1 - set
4785 * 2 - reset
4786 * 3 - permanently set
4787 * 4 - permanently reset
4788 */
4789 Terminal.prototype.requestAnsiMode = function(params) {
4790 ;
4791 };
4792
4793
4794 /**
4795 * CSI ? Ps$ p
4796 * Request DEC private mode (DECRQM). For VT300 and up, reply is
4797 * CSI ? Ps; Pm$ p
4798 * where Ps is the mode number as in DECSET, Pm is the mode value
4799 * as in the ANSI DECRQM.
4800 */
4801 Terminal.prototype.requestPrivateMode = function(params) {
4802 ;
4803 };
4804
4805
4806 /**
4807 * CSI Ps ; Ps " p
4808 * Set conformance level (DECSCL). Valid values for the first
4809 * parameter:
4810 * Ps = 6 1 -> VT100.
4811 * Ps = 6 2 -> VT200.
4812 * Ps = 6 3 -> VT300.
4813 * Valid values for the second parameter:
4814 * Ps = 0 -> 8-bit controls.
4815 * Ps = 1 -> 7-bit controls (always set for VT100).
4816 * Ps = 2 -> 8-bit controls.
4817 */
4818 Terminal.prototype.setConformanceLevel = function(params) {
4819 ;
4820 };
4821
4822
4823 /**
4824 * CSI Ps q Load LEDs (DECLL).
4825 * Ps = 0 -> Clear all LEDS (default).
4826 * Ps = 1 -> Light Num Lock.
4827 * Ps = 2 -> Light Caps Lock.
4828 * Ps = 3 -> Light Scroll Lock.
4829 * Ps = 2 1 -> Extinguish Num Lock.
4830 * Ps = 2 2 -> Extinguish Caps Lock.
4831 * Ps = 2 3 -> Extinguish Scroll Lock.
4832 */
4833 Terminal.prototype.loadLEDs = function(params) {
4834 ;
4835 };
4836
4837
4838 /**
4839 * CSI Ps SP q
4840 * Set cursor style (DECSCUSR, VT520).
4841 * Ps = 0 -> blinking block.
4842 * Ps = 1 -> blinking block (default).
4843 * Ps = 2 -> steady block.
4844 * Ps = 3 -> blinking underline.
4845 * Ps = 4 -> steady underline.
4846 */
4847 Terminal.prototype.setCursorStyle = function(params) {
4848 ;
4849 };
4850
4851
4852 /**
4853 * CSI Ps " q
4854 * Select character protection attribute (DECSCA). Valid values
4855 * for the parameter:
4856 * Ps = 0 -> DECSED and DECSEL can erase (default).
4857 * Ps = 1 -> DECSED and DECSEL cannot erase.
4858 * Ps = 2 -> DECSED and DECSEL can erase.
4859 */
4860 Terminal.prototype.setCharProtectionAttr = function(params) {
4861 ;
4862 };
4863
4864
4865 /**
4866 * CSI ? Pm r
4867 * Restore DEC Private Mode Values. The value of Ps previously
4868 * saved is restored. Ps values are the same as for DECSET.
4869 */
4870 Terminal.prototype.restorePrivateValues = function(params) {
4871 ;
4872 };
4873
4874
4875 /**
4876 * CSI Pt; Pl; Pb; Pr; Ps$ r
4877 * Change Attributes in Rectangular Area (DECCARA), VT400 and up.
4878 * Pt; Pl; Pb; Pr denotes the rectangle.
4879 * Ps denotes the SGR attributes to change: 0, 1, 4, 5, 7.
4880 * NOTE: xterm doesn't enable this code by default.
4881 */
4882 Terminal.prototype.setAttrInRectangle = function(params) {
4883 var t = params[0]
4884 , l = params[1]
4885 , b = params[2]
4886 , r = params[3]
4887 , attr = params[4];
4888
4889 var line
4890 , i;
4891
4892 for (; t < b + 1; t++) {
4893 line = this.lines[this.ybase + t];
4894 for (i = l; i < r; i++) {
4895 line[i] = [attr, line[i][1]];
4896 }
4897 }
4898
4899 // this.maxRange();
4900 this.updateRange(params[0]);
4901 this.updateRange(params[2]);
4902 };
4903
4904
4905 /**
4906 * CSI Pc; Pt; Pl; Pb; Pr$ x
4907 * Fill Rectangular Area (DECFRA), VT420 and up.
4908 * Pc is the character to use.
4909 * Pt; Pl; Pb; Pr denotes the rectangle.
4910 * NOTE: xterm doesn't enable this code by default.
4911 */
4912 Terminal.prototype.fillRectangle = function(params) {
4913 var ch = params[0]
4914 , t = params[1]
4915 , l = params[2]
4916 , b = params[3]
4917 , r = params[4];
4918
4919 var line
4920 , i;
4921
4922 for (; t < b + 1; t++) {
4923 line = this.lines[this.ybase + t];
4924 for (i = l; i < r; i++) {
4925 line[i] = [line[i][0], String.fromCharCode(ch)];
4926 }
4927 }
4928
4929 // this.maxRange();
4930 this.updateRange(params[1]);
4931 this.updateRange(params[3]);
4932 };
4933
4934
4935 /**
4936 * CSI Ps ; Pu ' z
4937 * Enable Locator Reporting (DECELR).
4938 * Valid values for the first parameter:
4939 * Ps = 0 -> Locator disabled (default).
4940 * Ps = 1 -> Locator enabled.
4941 * Ps = 2 -> Locator enabled for one report, then disabled.
4942 * The second parameter specifies the coordinate unit for locator
4943 * reports.
4944 * Valid values for the second parameter:
4945 * Pu = 0 <- or omitted -> default to character cells.
4946 * Pu = 1 <- device physical pixels.
4947 * Pu = 2 <- character cells.
4948 */
4949 Terminal.prototype.enableLocatorReporting = function(params) {
4950 var val = params[0] > 0;
4951 //this.mouseEvents = val;
4952 //this.decLocator = val;
4953 };
4954
4955
4956 /**
4957 * CSI Pt; Pl; Pb; Pr$ z
4958 * Erase Rectangular Area (DECERA), VT400 and up.
4959 * Pt; Pl; Pb; Pr denotes the rectangle.
4960 * NOTE: xterm doesn't enable this code by default.
4961 */
4962 Terminal.prototype.eraseRectangle = function(params) {
4963 var t = params[0]
4964 , l = params[1]
4965 , b = params[2]
4966 , r = params[3];
4967
4968 var line
4969 , i
4970 , ch;
4971
4972 ch = [this.eraseAttr(), ' ', 1]; // xterm?
4973
4974 for (; t < b + 1; t++) {
4975 line = this.lines[this.ybase + t];
4976 for (i = l; i < r; i++) {
4977 line[i] = ch;
4978 }
4979 }
4980
4981 // this.maxRange();
4982 this.updateRange(params[0]);
4983 this.updateRange(params[2]);
4984 };
4985
4986
4987 /**
4988 * CSI P m SP }
4989 * Insert P s Column(s) (default = 1) (DECIC), VT420 and up.
4990 * NOTE: xterm doesn't enable this code by default.
4991 */
4992 Terminal.prototype.insertColumns = function() {
4993 var param = params[0]
4994 , l = this.ybase + this.rows
4995 , ch = [this.eraseAttr(), ' ', 1] // xterm?
4996 , i;
4997
4998 while (param--) {
4999 for (i = this.ybase; i < l; i++) {
5000 this.lines[i].splice(this.x + 1, 0, ch);
5001 this.lines[i].pop();
5002 }
5003 }
5004
5005 this.maxRange();
5006 };
5007
5008
5009 /**
5010 * CSI P m SP ~
5011 * Delete P s Column(s) (default = 1) (DECDC), VT420 and up
5012 * NOTE: xterm doesn't enable this code by default.
5013 */
5014 Terminal.prototype.deleteColumns = function() {
5015 var param = params[0]
5016 , l = this.ybase + this.rows
5017 , ch = [this.eraseAttr(), ' ', 1] // xterm?
5018 , i;
5019
5020 while (param--) {
5021 for (i = this.ybase; i < l; i++) {
5022 this.lines[i].splice(this.x, 1);
5023 this.lines[i].push(ch);
5024 }
5025 }
5026
5027 this.maxRange();
5028 };
5029
5030 /**
5031 * Character Sets
5032 */
5033
5034 Terminal.charsets = {};
5035
5036 // DEC Special Character and Line Drawing Set.
5037 // http://vt100.net/docs/vt102-ug/table5-13.html
5038 // A lot of curses apps use this if they see TERM=xterm.
5039 // testing: echo -e '\e(0a\e(B'
5040 // The xterm output sometimes seems to conflict with the
5041 // reference above. xterm seems in line with the reference
5042 // when running vttest however.
5043 // The table below now uses xterm's output from vttest.
5044 Terminal.charsets.SCLD = { // (0
5045 '`': '\u25c6', // '◆'
5046 'a': '\u2592', // '▒'
5047 'b': '\u0009', // '\t'
5048 'c': '\u000c', // '\f'
5049 'd': '\u000d', // '\r'
5050 'e': '\u000a', // '\n'
5051 'f': '\u00b0', // '°'
5052 'g': '\u00b1', // '±'
5053 'h': '\u2424', // '\u2424' (NL)
5054 'i': '\u000b', // '\v'
5055 'j': '\u2518', // '┘'
5056 'k': '\u2510', // '┐'
5057 'l': '\u250c', // '┌'
5058 'm': '\u2514', // '└'
5059 'n': '\u253c', // '┼'
5060 'o': '\u23ba', // '⎺'
5061 'p': '\u23bb', // '⎻'
5062 'q': '\u2500', // '─'
5063 'r': '\u23bc', // '⎼'
5064 's': '\u23bd', // '⎽'
5065 't': '\u251c', // '├'
5066 'u': '\u2524', // '┤'
5067 'v': '\u2534', // '┴'
5068 'w': '\u252c', // '┬'
5069 'x': '\u2502', // '│'
5070 'y': '\u2264', // '≤'
5071 'z': '\u2265', // '≥'
5072 '{': '\u03c0', // 'π'
5073 '|': '\u2260', // '≠'
5074 '}': '\u00a3', // '£'
5075 '~': '\u00b7' // '·'
5076 };
5077
5078 Terminal.charsets.UK = null; // (A
5079 Terminal.charsets.US = null; // (B (USASCII)
5080 Terminal.charsets.Dutch = null; // (4
5081 Terminal.charsets.Finnish = null; // (C or (5
5082 Terminal.charsets.French = null; // (R
5083 Terminal.charsets.FrenchCanadian = null; // (Q
5084 Terminal.charsets.German = null; // (K
5085 Terminal.charsets.Italian = null; // (Y
5086 Terminal.charsets.NorwegianDanish = null; // (E or (6
5087 Terminal.charsets.Spanish = null; // (Z
5088 Terminal.charsets.Swedish = null; // (H or (7
5089 Terminal.charsets.Swiss = null; // (=
5090 Terminal.charsets.ISOLatin = null; // /A
5091
5092 /**
5093 * Helpers
5094 */
5095
5096 function contains(el, arr) {
5097 for (var i = 0; i < arr.length; i += 1) {
5098 if (el === arr[i]) {
5099 return true;
5100 }
5101 }
5102 return false;
5103 }
5104
5105 function on(el, type, handler, capture) {
5106 if (!Array.isArray(el)) {
5107 el = [el];
5108 }
5109 el.forEach(function (element) {
5110 element.addEventListener(type, handler, capture || false);
5111 });
5112 }
5113
5114 function off(el, type, handler, capture) {
5115 el.removeEventListener(type, handler, capture || false);
5116 }
5117
5118 function cancel(ev, force) {
5119 if (!this.cancelEvents && !force) {
5120 return;
5121 }
5122 ev.preventDefault();
5123 ev.stopPropagation();
5124 return false;
5125 }
5126
5127 function inherits(child, parent) {
5128 function f() {
5129 this.constructor = child;
5130 }
5131 f.prototype = parent.prototype;
5132 child.prototype = new f;
5133 }
5134
5135 // if bold is broken, we can't
5136 // use it in the terminal.
5137 function isBoldBroken(document) {
5138 var body = document.getElementsByTagName('body')[0];
5139 var el = document.createElement('span');
5140 el.innerHTML = 'hello world';
5141 body.appendChild(el);
5142 var w1 = el.scrollWidth;
5143 el.style.fontWeight = 'bold';
5144 var w2 = el.scrollWidth;
5145 body.removeChild(el);
5146 return w1 !== w2;
5147 }
5148
5149 function indexOf(obj, el) {
5150 var i = obj.length;
5151 while (i--) {
5152 if (obj[i] === el) return i;
5153 }
5154 return -1;
5155 }
5156
5157 function isThirdLevelShift(term, ev) {
5158 var thirdLevelKey =
5159 (term.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
5160 (term.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
5161
5162 if (ev.type == 'keypress') {
5163 return thirdLevelKey;
5164 }
5165
5166 // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)
5167 return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);
5168 }
5169
5170 function matchColor(r1, g1, b1) {
5171 var hash = (r1 << 16) | (g1 << 8) | b1;
5172
5173 if (matchColor._cache[hash] != null) {
5174 return matchColor._cache[hash];
5175 }
5176
5177 var ldiff = Infinity
5178 , li = -1
5179 , i = 0
5180 , c
5181 , r2
5182 , g2
5183 , b2
5184 , diff;
5185
5186 for (; i < Terminal.vcolors.length; i++) {
5187 c = Terminal.vcolors[i];
5188 r2 = c[0];
5189 g2 = c[1];
5190 b2 = c[2];
5191
5192 diff = matchColor.distance(r1, g1, b1, r2, g2, b2);
5193
5194 if (diff === 0) {
5195 li = i;
5196 break;
5197 }
5198
5199 if (diff < ldiff) {
5200 ldiff = diff;
5201 li = i;
5202 }
5203 }
5204
5205 return matchColor._cache[hash] = li;
5206 }
5207
5208 matchColor._cache = {};
5209
5210 // http://stackoverflow.com/questions/1633828
5211 matchColor.distance = function(r1, g1, b1, r2, g2, b2) {
5212 return Math.pow(30 * (r1 - r2), 2)
5213 + Math.pow(59 * (g1 - g2), 2)
5214 + Math.pow(11 * (b1 - b2), 2);
5215 };
5216
5217 function each(obj, iter, con) {
5218 if (obj.forEach) return obj.forEach(iter, con);
5219 for (var i = 0; i < obj.length; i++) {
5220 iter.call(con, obj[i], i, obj);
5221 }
5222 }
5223
5224 function keys(obj) {
5225 if (Object.keys) return Object.keys(obj);
5226 var key, keys = [];
5227 for (key in obj) {
5228 if (Object.prototype.hasOwnProperty.call(obj, key)) {
5229 keys.push(key);
5230 }
5231 }
5232 return keys;
5233 }
5234
5235 var wcwidth = (function(opts) {
5236 // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c
5237 // combining characters
5238 var COMBINING = [
5239 [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
5240 [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
5241 [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
5242 [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
5243 [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
5244 [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
5245 [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
5246 [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
5247 [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
5248 [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
5249 [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
5250 [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
5251 [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
5252 [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
5253 [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
5254 [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
5255 [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
5256 [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
5257 [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
5258 [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
5259 [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
5260 [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
5261 [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
5262 [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
5263 [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
5264 [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
5265 [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
5266 [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
5267 [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
5268 [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
5269 [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
5270 [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
5271 [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
5272 [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
5273 [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
5274 [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
5275 [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
5276 [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
5277 [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
5278 [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
5279 [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
5280 [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
5281 [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],
5282 [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
5283 [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
5284 [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
5285 [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
5286 [0xE0100, 0xE01EF]
5287 ];
5288 // binary search
5289 function bisearch(ucs) {
5290 var min = 0;
5291 var max = COMBINING.length - 1;
5292 var mid;
5293 if (ucs < COMBINING[0][0] || ucs > COMBINING[max][1])
5294 return false;
5295 while (max >= min) {
5296 mid = Math.floor((min + max) / 2);
5297 if (ucs > COMBINING[mid][1])
5298 min = mid + 1;
5299 else if (ucs < COMBINING[mid][0])
5300 max = mid - 1;
5301 else
5302 return true;
5303 }
5304 return false;
5305 }
5306 function wcwidth(ucs) {
5307 // test for 8-bit control characters
5308 if (ucs === 0)
5309 return opts.nul;
5310 if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
5311 return opts.control;
5312 // binary search in table of non-spacing characters
5313 if (bisearch(ucs))
5314 return 0;
5315 // if we arrive here, ucs is not a combining or C0/C1 control character
5316 return 1 +
5317 (
5318 ucs >= 0x1100 &&
5319 (
5320 ucs <= 0x115f || // Hangul Jamo init. consonants
5321 ucs == 0x2329 ||
5322 ucs == 0x232a ||
5323 (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs != 0x303f) || // CJK..Yi
5324 (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables
5325 (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs
5326 (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
5327 (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms
5328 (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
5329 (ucs >= 0xffe0 && ucs <= 0xffe6) ||
5330 (ucs >= 0x20000 && ucs <= 0x2fffd) ||
5331 (ucs >= 0x30000 && ucs <= 0x3fffd)
5332 )
5333 );
5334 }
5335 return wcwidth;
5336 })({nul: 0, control: 0}); // configurable options
5337
5338 /**
5339 * Expose
5340 */
5341
5342 Terminal.EventEmitter = EventEmitter;
5343 Terminal.CompositionHelper = CompositionHelper;
5344 Terminal.Viewport = Viewport;
5345 Terminal.inherits = inherits;
5346
5347 /**
5348 * Adds an event listener to the terminal.
5349 *
5350 * @param {string} event The name of the event. TODO: Document all event types
5351 * @param {function} callback The function to call when the event is triggered.
5352 */
5353 Terminal.on = on;
5354 Terminal.off = off;
5355 Terminal.cancel = cancel;
5356
5357 module.exports = Terminal;