blob: ea4d5953fc1a1f5a11d4eda9ad1b3a8cc3b70e13 [file] [log] [blame]
rginda8ba33642011-12-14 12:31:31 -08001// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * Constructor for the Terminal class.
7 *
8 * A Terminal pulls together the hterm.ScrollPort, hterm.Screen and hterm.VT100
9 * classes to provide the complete terminal functionality.
10 *
11 * There are a number of lower-level Terminal methods that can be called
12 * directly to manipulate the cursor, text, scroll region, and other terminal
13 * attributes. However, the primary method is interpret(), which parses VT
14 * escape sequences and invokes the appropriate Terminal methods.
15 *
16 * This class was heavily influenced by Cory Maccarrone's Framebuffer class.
17 *
18 * TODO(rginda): Eventually we're going to need to support characters which are
19 * displayed twice as wide as standard latin characters. This is to support
20 * CJK (and possibly other character sets).
21 */
22hterm.Terminal = function() {
23 // Two screen instances.
24 this.primaryScreen_ = new hterm.Screen();
25 this.alternateScreen_ = new hterm.Screen();
26
27 // The "current" screen.
28 this.screen_ = this.primaryScreen_;
29
30 // The VT escape sequence interpreter.
31 this.vt100_ = new hterm.VT100(this);
32
33 // The local notion of the screen size. ScreenBuffers also have a size which
34 // indicates their present size. During size changes, the two may disagree.
35 // Also, the inactive screen's size is not altered until it is made the active
36 // screen.
37 this.screenSize = new hterm.Size(0, 0);
38
39 // The pixel dimensions of a single character on the screen.
40 this.characterSize_ = new hterm.Size(0, 0);
41
42 // The scroll port we'll be using to display the visible rows.
43 this.scrollPort_ = new hterm.ScrollPort(this, 15);
44 this.scrollPort_.subscribe('resize', this.onResize_.bind(this));
45 this.scrollPort_.subscribe('scroll', this.onScroll_.bind(this));
46
47 // The rows that have scrolled off screen and are no longer addressable.
48 this.scrollbackRows_ = [];
49
50 // The VT's notion of the top and bottom rows. Used during some VT
51 // cursor positioning and scrolling commands.
52 this.vtScrollTop_ = null;
53 this.vtScrollBottom_ = null;
54
55 // The DIV element for the visible cursor.
56 this.cursorNode_ = null;
57
58 // The default colors for text with no other color attributes.
59 this.backgroundColor = 'black';
60 this.foregroundColor = 'white';
61
62 // The color of the cursor.
63 this.cursorColor = 'rgba(255,0,0,0.5)';
64
65 // The current mode bits for the terminal.
66 this.options_ = new hterm.Options();
67
68 // Timeouts we might need to clear.
69 this.timeouts_ = {};
70};
71
72/**
73 * Methods called by Cory's vt100 interpreter which we haven't implemented yet.
74 */
75hterm.Terminal.prototype.reset =
76hterm.Terminal.prototype.clearColorAndAttributes =
77hterm.Terminal.prototype.setForegroundColor256 =
78hterm.Terminal.prototype.setBackgroundColor256 =
79hterm.Terminal.prototype.setForegroundColor =
80hterm.Terminal.prototype.setBackgroundColor =
81hterm.Terminal.prototype.setAttributes =
82hterm.Terminal.prototype.resize =
83hterm.Terminal.prototype.setSpecialCharactersEnabled =
84hterm.Terminal.prototype.setTabStopAtCursor =
85hterm.Terminal.prototype.clearTabStops =
86hterm.Terminal.prototype.saveCursor =
87hterm.Terminal.prototype.restoreCursor =
88hterm.Terminal.prototype.reverseLineFeed = function() {
89 throw 'NOT IMPLEMENTED';
90};
91
92/**
93 * Interpret a sequence of characters.
94 *
95 * Incomplete escape sequences are buffered until the next call.
96 *
97 * @param {string} str Sequence of characters to interpret or pass through.
98 */
99hterm.Terminal.prototype.interpret = function(str) {
100 this.vt100_.interpretString(str);
101 this.scheduleSyncCursorPosition_();
102};
103
104/**
105 * Take over the given DIV for use as the terminal display.
106 *
107 * @param {HTMLDivElement} div The div to use as the terminal display.
108 */
109hterm.Terminal.prototype.decorate = function(div) {
110 this.scrollPort_.decorate(div);
111 this.document_ = this.scrollPort_.getDocument();
112
113 // Get character dimensions from the scrollPort.
114 this.characterSize_.height = this.scrollPort_.getRowHeight();
115 this.characterSize_.width = this.scrollPort_.getCharacterWidth();
116
117 this.cursorNode_ = this.document_.createElement('div');
118 this.cursorNode_.style.cssText =
119 ('position: absolute;' +
120 'display: none;' +
121 'width: ' + this.characterSize_.width + 'px;' +
122 'height: ' + this.characterSize_.height + 'px;' +
123 'background-color: ' + this.cursorColor);
124 this.document_.body.appendChild(this.cursorNode_);
125
126 this.setReverseVideo(false);
127};
128
129/**
130 * Return the HTML Element for a given row index.
131 *
132 * This is a method from the RowProvider interface. The ScrollPort uses
133 * it to fetch rows on demand as they are scrolled into view.
134 *
135 * TODO(rginda): Consider saving scrollback rows as (HTML source, text content)
136 * pairs to conserve memory.
137 *
138 * @param {integer} index The zero-based row index, measured relative to the
139 * start of the scrollback buffer. On-screen rows will always have the
140 * largest indicies.
141 * @return {HTMLElement} The 'x-row' element containing for the requested row.
142 */
143hterm.Terminal.prototype.getRowNode = function(index) {
144 if (index < this.scrollbackRows_.length)
145 return this.scrollbackRows_[index];
146
147 var screenIndex = index - this.scrollbackRows_.length;
148 return this.screen_.rowsArray[screenIndex];
149};
150
151/**
152 * Return the text content for a given range of rows.
153 *
154 * This is a method from the RowProvider interface. The ScrollPort uses
155 * it to fetch text content on demand when the user attempts to copy their
156 * selection to the clipboard.
157 *
158 * @param {integer} start The zero-based row index to start from, measured
159 * relative to the start of the scrollback buffer. On-screen rows will
160 * always have the largest indicies.
161 * @param {integer} end The zero-based row index to end on, measured
162 * relative to the start of the scrollback buffer.
163 * @return {string} A single string containing the text value of the range of
164 * rows. Lines will be newline delimited, with no trailing newline.
165 */
166hterm.Terminal.prototype.getRowsText = function(start, end) {
167 var ary = [];
168 for (var i = start; i < end; i++) {
169 var node = this.getRowNode(i);
170 ary.push(node.textContent);
171 }
172
173 return ary.join('\n');
174};
175
176/**
177 * Return the text content for a given row.
178 *
179 * This is a method from the RowProvider interface. The ScrollPort uses
180 * it to fetch text content on demand when the user attempts to copy their
181 * selection to the clipboard.
182 *
183 * @param {integer} index The zero-based row index to return, measured
184 * relative to the start of the scrollback buffer. On-screen rows will
185 * always have the largest indicies.
186 * @return {string} A string containing the text value of the selected row.
187 */
188hterm.Terminal.prototype.getRowText = function(index) {
189 var node = this.getRowNode(index);
190 return row.textContent;
191};
192
193/**
194 * Return the total number of rows in the addressable screen and in the
195 * scrollback buffer of this terminal.
196 *
197 * This is a method from the RowProvider interface. The ScrollPort uses
198 * it to compute the size of the scrollbar.
199 *
200 * @return {integer} The number of rows in this terminal.
201 */
202hterm.Terminal.prototype.getRowCount = function() {
203 return this.scrollbackRows_.length + this.screen_.rowsArray.length;
204};
205
206/**
207 * Create DOM nodes for new rows and append them to the end of the terminal.
208 *
209 * This is the only correct way to add a new DOM node for a row. Notice that
210 * the new row is appended to the bottom of the list of rows, and does not
211 * require renumbering (of the rowIndex property) of previous rows.
212 *
213 * If you think you want a new blank row somewhere in the middle of the
214 * terminal, look into moveRows_().
215 *
216 * This method does not pay attention to vtScrollTop/Bottom, since you should
217 * be using moveRows() in cases where they would matter.
218 *
219 * The cursor will be positioned at column 0 of the first inserted line.
220 */
221hterm.Terminal.prototype.appendRows_ = function(count) {
222 var cursorRow = this.screen_.rowsArray.length;
223 var offset = this.scrollbackRows_.length + cursorRow;
224 for (var i = 0; i < count; i++) {
225 var row = this.document_.createElement('x-row');
226 row.appendChild(this.document_.createTextNode(''));
227 row.rowIndex = offset + i;
228 this.screen_.pushRow(row);
229 }
230
231 var extraRows = this.screen_.rowsArray.length - this.screenSize.height;
232 if (extraRows > 0) {
233 var ary = this.screen_.shiftRows(extraRows);
234 Array.prototype.push.apply(this.scrollbackRows_, ary);
235 this.scheduleScrollDown_();
236 }
237
238 if (cursorRow >= this.screen_.rowsArray.length)
239 cursorRow = this.screen_.rowsArray.length - 1;
240
241 this.screen_.setCursorPosition(cursorRow, 0);
242};
243
244/**
245 * Relocate rows from one part of the addressable screen to another.
246 *
247 * This is used to recycle rows during VT scrolls (those which are driven
248 * by VT commands, rather than by the user manipulating the scrollbar.)
249 *
250 * In this case, the blank lines scrolled into the scroll region are made of
251 * the nodes we scrolled off. These have their rowIndex properties carefully
252 * renumbered so as not to confuse the ScrollPort.
253 *
254 * TODO(rginda): I'm not sure why this doesn't require a scrollport repaint.
255 * It may just be luck. I wouldn't be surprised if we actually needed to call
256 * scrollPort_.invalidateRowRange, but I'm going to wait for evidence before
257 * adding it.
258 */
259hterm.Terminal.prototype.moveRows_ = function(fromIndex, count, toIndex) {
260 var ary = this.screen_.removeRows(fromIndex, count);
261 this.screen_.insertRows(toIndex, ary);
262
263 var start, end;
264 if (fromIndex < toIndex) {
265 start = fromIndex;
266 end = fromIndex + count;
267 } else {
268 start = toIndex;
269 end = toIndex + count;
270 }
271
272 this.renumberRows_(start, end);
273};
274
275/**
276 * Renumber the rowIndex property of the given range of rows.
277 *
278 * The start and end indicies are relative to the screen, not the scrollback.
279 * Rows in the scrollback buffer cannot be renumbered. Since they are not
280 * addressable (you cant delete them, scroll them, etc), you should have
281 * no need to renumber scrollback rows.
282 */
283hterm.Terminal.prototype.renumberRows_ = function(start, end) {
284 var offset = this.scrollbackRows_.length;
285 for (var i = start; i < end; i++) {
286 this.screen_.rowsArray[i].rowIndex = offset + i;
287 }
288};
289
290/**
291 * Print a string to the terminal.
292 *
293 * This respects the current insert and wraparound modes. It will add new lines
294 * to the end of the terminal, scrolling off the top into the scrollback buffer
295 * if necessary.
296 *
297 * The string is *not* parsed for escape codes. Use the interpret() method if
298 * that's what you're after.
299 *
300 * @param{string} str The string to print.
301 */
302hterm.Terminal.prototype.print = function(str) {
303 do {
304 if (this.options_.insertMode) {
305 str = this.screen_.insertString(str);
306 } else {
307 str = this.screen_.overwriteString(str);
308 }
309
310 if (this.options_.wraparound && str) {
311 this.newLine();
312 } else {
313 break;
314 }
315 } while (str);
316
317 this.scheduleSyncCursorPosition_();
318};
319
320/**
321 * Return the top row index according to the VT.
322 *
323 * This will return 0 unless the terminal has been told to restrict scrolling
324 * to some lower row. It is used for some VT cursor positioning and scrolling
325 * commands.
326 *
327 * @return {integer} The topmost row in the terminal's scroll region.
328 */
329hterm.Terminal.prototype.getVTScrollTop = function() {
330 if (this.vtScrollTop_ != null)
331 return this.vtScrollTop_;
332
333 return 0;
334}
335
336/**
337 * Return the bottom row index according to the VT.
338 *
339 * This will return the height of the terminal unless the it has been told to
340 * restrict scrolling to some higher row. It is used for some VT cursor
341 * positioning and scrolling commands.
342 *
343 * @return {integer} The bottommost row in the terminal's scroll region.
344 */
345hterm.Terminal.prototype.getVTScrollBottom = function() {
346 if (this.vtScrollBottom_ != null)
347 return this.vtScrollBottom_;
348
349 return this.screenSize.height;
350}
351
352/**
353 * Process a '\n' character.
354 *
355 * If the cursor is on the final row of the terminal this will append a new
356 * blank row to the screen and scroll the topmost row into the scrollback
357 * buffer.
358 *
359 * Otherwise, this moves the cursor to column zero of the next row.
360 */
361hterm.Terminal.prototype.newLine = function() {
362 if (this.screen_.cursorPosition.row == this.screen_.rowsArray.length - 1) {
363 this.appendRows_(1);
364 } else {
365 this.screen_.setCursorPosition(this.screen_.cursorPosition.row + 1, 0);
366 }
367};
368
369/**
370 * Like newLine(), except maintain the cursor column.
371 */
372hterm.Terminal.prototype.lineFeed = function() {
373 var column = this.screen_.cursorPosition.column;
374 this.newLine();
375 this.setCursorColumn(column);
376};
377
378/**
379 * Replace all characters to the left of the current cursor with the space
380 * character.
381 *
382 * TODO(rginda): This should probably *remove* the characters (not just replace
383 * with a space) if there are no characters at or beyond the current cursor
384 * position. Once it does that, it'll have the same text-attribute related
385 * issues as hterm.Screen.prototype.clearCursorRow :/
386 */
387hterm.Terminal.prototype.eraseToLeft = function() {
388 var currentColumn = this.screen_.cursorPosition.column;
389 this.setCursorColumn(0);
390 this.screen_.overwriteString(hterm.getWhitespace(currentColumn + 1));
391 this.setCursorColumn(currentColumn);
392};
393
394/**
395 * Erase a given number of characters to the right of the cursor, shifting
396 * remaining characters to the left.
397 *
398 * The cursor position is unchanged.
399 *
400 * TODO(rginda): Test that this works even when the cursor is positioned beyond
401 * the end of the text.
402 *
403 * TODO(rginda): This likely has text-attribute related troubles similar to the
404 * todo on hterm.Screen.prototype.clearCursorRow.
405 */
406hterm.Terminal.prototype.eraseToRight = function(opt_count) {
407 var currentColumn = this.screen_.cursorPosition.column;
408
409 var maxCount = this.screenSize.width - currentColumn;
410 var count = (opt_count && opt_count < maxCount) ? opt_count : maxCount;
411 this.screen_.deleteChars(count);
412 this.setCursorColumn(currentColumn);
413};
414
415/**
416 * Erase the current line.
417 *
418 * The cursor position is unchanged.
419 *
420 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
421 * has a text-attribute related TODO.
422 */
423hterm.Terminal.prototype.eraseLine = function() {
424 var currentColumn = this.screen_.cursorPosition.column;
425 this.screen_.clearCursorRow();
426 this.setCursorColumn(currentColumn);
427};
428
429/**
430 * Erase all characters from the start of the scroll region to the current
431 * cursor position.
432 *
433 * The cursor position is unchanged.
434 *
435 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
436 * has a text-attribute related TODO.
437 */
438hterm.Terminal.prototype.eraseAbove = function() {
439 var currentRow = this.screen_.cursorPosition.row;
440 var currentColumn = this.screen_.cursorPosition.column;
441
442 var top = this.getVTScrollTop();
443 for (var i = top; i < currentRow; i++) {
444 this.screen_.setCursorPosition(i, 0);
445 this.screen_.clearCursorRow();
446 }
447
448 this.screen_.setCursorPosition(currentRow, currentColumn);
449};
450
451/**
452 * Erase all characters from the current cursor position to the end of the
453 * scroll region.
454 *
455 * The cursor position is unchanged.
456 *
457 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
458 * has a text-attribute related TODO.
459 */
460hterm.Terminal.prototype.eraseBelow = function() {
461 var currentRow = this.screen_.cursorPosition.row;
462 var currentColumn = this.screen_.cursorPosition.column;
463
464 var bottom = this.getVTScrollBottom();
465 for (var i = currentRow + 1; i < bottom; i++) {
466 this.screen_.setCursorPosition(i, 0);
467 this.screen_.clearCursorRow();
468 }
469
470 this.screen_.setCursorPosition(currentRow, currentColumn);
471};
472
473/**
474 * Erase the entire scroll region.
475 *
476 * The cursor position is unchanged.
477 *
478 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
479 * has a text-attribute related TODO.
480 */
481hterm.Terminal.prototype.clear = function() {
482 var currentRow = this.screen_.cursorPosition.row;
483 var currentColumn = this.screen_.cursorPosition.column;
484
485 var top = this.getVTScrollTop();
486 var bottom = this.getVTScrollBottom();
487
488 for (var i = top; i < bottom; i++) {
489 this.screen_.setCursorPosition(i, 0);
490 this.screen_.clearCursorRow();
491 }
492
493 this.screen_.setCursorPosition(currentRow, currentColumn);
494};
495
496/**
497 * VT command to insert lines at the current cursor row.
498 *
499 * This respects the current scroll region. Rows pushed off the bottom are
500 * lost (they won't show up in the scrollback buffer).
501 *
502 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
503 * has a text-attribute related TODO.
504 *
505 * @param {integer} count The number of lines to insert.
506 */
507hterm.Terminal.prototype.insertLines = function(count) {
508 var currentRow = this.screen_.cursorPosition.row;
509
510 var bottom = this.getVTScrollBottom();
511 count = Math.min(count, bottom - currentRow);
512
513 var start = bottom - count;
514 if (start != currentRow)
515 this.moveRows_(start, count, currentRow);
516
517 for (var i = 0; i < count; i++) {
518 this.screen_.setCursorPosition(currentRow + i, 0);
519 this.screen_.clearCursorRow();
520 }
521
522 this.screen_.setCursorPosition(currentRow, 0);
523};
524
525/**
526 * VT command to delete lines at the current cursor row.
527 *
528 * New rows are added to the bottom of scroll region to take their place. New
529 * rows are strictly there to take up space and have no content or style.
530 */
531hterm.Terminal.prototype.deleteLines = function(count) {
532 var currentRow = this.screen_.cursorPosition.row;
533 var currentColumn = this.screen_.cursorPosition.column;
534
535 var top = currentRow;
536 var bottom = this.getVTScrollBottom();
537
538 var maxCount = bottom - top;
539 count = Math.min(count, maxCount);
540
541 var moveStart = bottom - count;
542 if (count != maxCount)
543 this.moveRows_(top, count, moveStart);
544
545 for (var i = 0; i < count; i++) {
546 this.screen_.setCursorPosition(moveStart + i, 0);
547 this.screen_.clearCursorRow();
548 }
549
550 this.screen_.setCursorPosition(currentRow, currentColumn);
551};
552
553/**
554 * Inserts the given number of spaces at the current cursor position.
555 *
556 * The cursor is left at the end of the inserted spaces.
557 */
558hterm.Terminal.prototype.insertSpace = function(count) {
559 var ws = hterm.getWhitespace(count);
560 this.screen_.insertString(ws);
561};
562
563/**
564 * Forward-delete the specified number of characters starting at the cursor
565 * position.
566 *
567 * @param {integer} count The number of characters to delete.
568 */
569hterm.Terminal.prototype.deleteChars = function(count) {
570 this.screen_.deleteChars(count);
571};
572
573/**
574 * Shift rows in the scroll region upwards by a given number of lines.
575 *
576 * New rows are inserted at the bottom of the scroll region to fill the
577 * vacated rows. The new rows not filled out with the current text attributes.
578 *
579 * This function does not affect the scrollback rows at all. Rows shifted
580 * off the top are lost.
581 *
582 * @param {integer} count The number of rows to scroll.
583 */
584hterm.Terminal.prototype.vtScrollUp = function(count) {
585 var currentRow = this.screen_.cursorPosition.row;
586 var currentColumn = this.screen_.cursorPosition.column;
587
588 this.setCursorRow(this.getVTScrollTop());
589 this.deleteLines(count);
590
591 this.screen_.setCursorPosition(currentRow, currentColumn);
592};
593
594/**
595 * Shift rows below the cursor down by a given number of lines.
596 *
597 * This function respects the current scroll region.
598 *
599 * New rows are inserted at the top of the scroll region to fill the
600 * vacated rows. The new rows not filled out with the current text attributes.
601 *
602 * This function does not affect the scrollback rows at all. Rows shifted
603 * off the bottom are lost.
604 *
605 * @param {integer} count The number of rows to scroll.
606 */
607hterm.Terminal.prototype.vtScrollDown = function(opt_count) {
608 var currentRow = this.screen_.cursorPosition.row;
609 var currentColumn = this.screen_.cursorPosition.column;
610
611 this.setCursorRow(this.getVTScrollTop());
612 this.insertLines(opt_count);
613
614 this.screen_.setCursorPosition(currentRow, currentColumn);
615};
616
617/**
618 * Set the cursor position.
619 *
620 * The cursor row is relative to the scroll region if the terminal has
621 * 'origin mode' enabled, or relative to the addressable screen otherwise.
622 *
623 * @param {integer} row The new zero-based cursor row.
624 * @param {integer} row The new zero-based cursor column.
625 */
626hterm.Terminal.prototype.setCursorPosition = function(row, column) {
627 if (this.options_.originMode) {
628 var scrollTop = this.getScrollTop();
629 row = hterm.clamp(row + scrollTop, scrollTop, this.getScrollBottom());
630 } else {
631 row = hterm.clamp(row, 0, this.screenSize.height);
632 }
633
634 this.screen_.setCursorPosition(row, column);
635};
636
637/**
638 * Set the cursor column.
639 *
640 * @param {integer} column The new zero-based cursor column.
641 */
642hterm.Terminal.prototype.setCursorColumn = function(column) {
643 this.screen_.setCursorPosition(this.screen_.cursorPosition.row, column);
644};
645
646/**
647 * Return the cursor column.
648 *
649 * @return {integer} The zero-based cursor column.
650 */
651hterm.Terminal.prototype.getCursorColumn = function() {
652 return this.screen_.cursorPosition.column;
653};
654
655/**
656 * Set the cursor row.
657 *
658 * The cursor row is relative to the scroll region if the terminal has
659 * 'origin mode' enabled, or relative to the addressable screen otherwise.
660 *
661 * @param {integer} row The new cursor row.
662 */
663hterm.Terminal.prototype.setCursorRow = function(row) {
664 this.setCursorPosition(row, this.screen_.cursorPosition.column);
665};
666
667/**
668 * Return the cursor row.
669 *
670 * @return {integer} The zero-based cursor row.
671 */
672hterm.Terminal.prototype.getCursorRow = function(row) {
673 return this.screen_.cursorPosition.row;
674};
675
676/**
677 * Request that the ScrollPort redraw itself soon.
678 *
679 * The redraw will happen asynchronously, soon after the call stack winds down.
680 * Multiple calls will be coalesced into a single redraw.
681 */
682hterm.Terminal.prototype.scheduleRedraw_ = function() {
683 if (this.redrawTimeout_)
684 clearTimeout(this.redrawTimeout_);
685
686 var self = this;
687 setTimeout(function() {
688 self.redrawTimeout_ = null;
689 self.scrollPort_.redraw_();
690 }, 0);
691};
692
693/**
694 * Request that the ScrollPort be scrolled to the bottom.
695 *
696 * The scroll will happen asynchronously, soon after the call stack winds down.
697 * Multiple calls will be coalesced into a single scroll.
698 *
699 * This affects the scrollbar position of the ScrollPort, and has nothing to
700 * do with the VT scroll commands.
701 */
702hterm.Terminal.prototype.scheduleScrollDown_ = function() {
703 if (this.timeouts_.scrollDown)
704 clearTimeout(this.timeouts_.scrollDown);
705
706 var self = this;
707 this.timeouts_.scrollDown = setTimeout(function() {
708 delete self.timeouts_.scrollDown;
709 self.scrollPort_.scrollRowToBottom(self.getRowCount());
710 }, 10);
711};
712
713/**
714 * Move the cursor up a specified number of rows.
715 *
716 * @param {integer} count The number of rows to move the cursor.
717 */
718hterm.Terminal.prototype.cursorUp = function(count) {
719 return this.cursorDown(-count);
720};
721
722/**
723 * Move the cursor down a specified number of rows.
724 *
725 * @param {integer} count The number of rows to move the cursor.
726 */
727hterm.Terminal.prototype.cursorDown = function(count) {
728 var minHeight = (this.options_.originMode ? this.getVTScrollTop() : 0);
729 var maxHeight = (this.options_.originMode ? this.getVTScrollBottom() :
730 this.screenSize.height - 1);
731
732 var row = hterm.clamp(this.screen_.cursorPosition.row + count,
733 minHeight, maxHeight);
734 this.setCursorRow(row);
735};
736
737/**
738 * Move the cursor left a specified number of columns.
739 *
740 * @param {integer} count The number of columns to move the cursor.
741 */
742hterm.Terminal.prototype.cursorLeft = function(count) {
743 return this.cursorRight(-count);
744};
745
746/**
747 * Move the cursor right a specified number of columns.
748 *
749 * @param {integer} count The number of columns to move the cursor.
750 */
751hterm.Terminal.prototype.cursorRight = function(count) {
752 var column = hterm.clamp(this.screen_.cursorPosition.column + count,
753 0, this.screenSize.width);
754 this.setCursorColumn(column);
755};
756
757/**
758 * Reverse the foreground and background colors of the terminal.
759 *
760 * This only affects text that was drawn with no attributes.
761 *
762 * TODO(rginda): Test xterm to see if reverse is respected for text that has
763 * been drawn with attributes that happen to coincide with the default
764 * 'no-attribute' colors. My guess is probably not.
765 */
766hterm.Terminal.prototype.setReverseVideo = function(state) {
767 if (state) {
768 this.scrollPort_.setForegroundColor(this.backgroundColor);
769 this.scrollPort_.setBackgroundColor(this.foregroundColor);
770 } else {
771 this.scrollPort_.setForegroundColor(this.foregroundColor);
772 this.scrollPort_.setBackgroundColor(this.backgroundColor);
773 }
774};
775
776/**
777 * Set the origin mode bit.
778 *
779 * If origin mode is on, certain VT cursor and scrolling commands measure their
780 * row parameter relative to the VT scroll region. Otherwise, row 0 corresponds
781 * to the top of the addressable screen.
782 *
783 * Defaults to off.
784 *
785 * @param {boolean} state True to set origin mode, false to unset.
786 */
787hterm.Terminal.prototype.setOriginMode = function(state) {
788 this.options_.originMode = state;
789};
790
791/**
792 * Set the insert mode bit.
793 *
794 * If insert mode is on, existing text beyond the cursor position will be
795 * shifted right to make room for new text. Otherwise, new text overwrites
796 * any existing text.
797 *
798 * Defaults to off.
799 *
800 * @param {boolean} state True to set insert mode, false to unset.
801 */
802hterm.Terminal.prototype.setInsertMode = function(state) {
803 this.options_.insertMode = state;
804};
805
806/**
807 * Set the wraparound mode bit.
808 *
809 * If wraparound mode is on, certain VT commands will allow the cursor to wrap
810 * to the start of the following row. Otherwise, the cursor is clamped to the
811 * end of the screen and attempts to write past it are ignored.
812 *
813 * Defaults to on.
814 *
815 * @param {boolean} state True to set wraparound mode, false to unset.
816 */
817hterm.Terminal.prototype.setWraparound = function(state) {
818 this.options_.wraparound = state;
819};
820
821/**
822 * Set the reverse-wraparound mode bit.
823 *
824 * If wraparound mode is off, certain VT commands will allow the cursor to wrap
825 * to the end of the previous row. Otherwise, the cursor is clamped to column
826 * 0.
827 *
828 * Defaults to off.
829 *
830 * @param {boolean} state True to set reverse-wraparound mode, false to unset.
831 */
832hterm.Terminal.prototype.setReverseWraparound = function(state) {
833 this.options_.reverseWraparound = state;
834};
835
836/**
837 * Selects between the primary and alternate screens.
838 *
839 * If alternate mode is on, the alternate screen is active. Otherwise the
840 * primary screen is active.
841 *
842 * Swapping screens has no effect on the scrollback buffer.
843 *
844 * Each screen maintains its own cursor position.
845 *
846 * Defaults to off.
847 *
848 * @param {boolean} state True to set alternate mode, false to unset.
849 */
850hterm.Terminal.prototype.setAlternateMode = function(state) {
851 this.screen_ = state ? this.alternateScreen_ : this.primaryScreen_;
852
853 this.screen_.setColumnCount(this.screenSize.width);
854
855 var rowDelta = this.screenSize.height - this.screen_.getHeight();
856 if (rowDelta > 0)
857 this.appendRows_(rowDelta);
858
859 this.scrollPort_.invalidateRowRange(
860 this.scrollbackRows_.length,
861 this.scrollbackRows_.length + this.screenSize.height);
862
863 if (this.screen_.cursorPosition.row == -1)
864 this.screen_.setCursorPosition(0, 0);
865
866 this.syncCursorPosition_();
867};
868
869/**
870 * Set the cursor-blink mode bit.
871 *
872 * If cursor-blink is on, the cursor will blink when it is visible. Otherwise
873 * a visible cursor does not blink.
874 *
875 * You should make sure to turn blinking off if you're going to dispose of a
876 * terminal, otherwise you'll leak a timeout.
877 *
878 * Defaults to on.
879 *
880 * @param {boolean} state True to set cursor-blink mode, false to unset.
881 */
882hterm.Terminal.prototype.setCursorBlink = function(state) {
883 this.options_.cursorBlink = state;
884
885 if (!state && this.timeouts_.cursorBlink) {
886 clearTimeout(this.timeouts_.cursorBlink);
887 delete this.timeouts_.cursorBlink;
888 }
889
890 if (this.options_.cursorVisible)
891 this.setCursorVisible(true);
892};
893
894/**
895 * Set the cursor-visible mode bit.
896 *
897 * If cursor-visible is on, the cursor will be visible. Otherwise it will not.
898 *
899 * Defaults to on.
900 *
901 * @param {boolean} state True to set cursor-visible mode, false to unset.
902 */
903hterm.Terminal.prototype.setCursorVisible = function(state) {
904 this.options_.cursorVisible = state;
905
906 if (!state) {
907 this.cursorNode_.style.display = 'none';
908 return;
909 }
910
911 this.cursorNode_.style.display = 'block';
912
913 if (this.options_.cursorBlink) {
914 if (this.timeouts_.cursorBlink)
915 return;
916
917 this.timeouts_.cursorBlink = setInterval(this.onCursorBlink_.bind(this),
918 500);
919 } else {
920 if (this.timeouts_.cursorBlink) {
921 clearTimeout(this.timeouts_.cursorBlink);
922 delete this.timeouts_.cursorBlink;
923 }
924 }
925};
926
927/**
928 * Synchronizes the visible cursor with the current cursor coordinates.
929 */
930hterm.Terminal.prototype.syncCursorPosition_ = function() {
931 var topRowIndex = this.scrollPort_.getTopRowIndex();
932 var bottomRowIndex = this.scrollPort_.getBottomRowIndex(topRowIndex);
933 var cursorRowIndex = this.scrollbackRows_.length +
934 this.screen_.cursorPosition.row;
935
936 if (cursorRowIndex > bottomRowIndex) {
937 // Cursor is scrolled off screen, move it outside of the visible area.
938 this.cursorNode_.style.top = -this.characterSize_.height;
939 return;
940 }
941
942 this.cursorNode_.style.top = this.scrollPort_.visibleRowTopMargin +
943 this.characterSize_.height * (cursorRowIndex - topRowIndex);
944 this.cursorNode_.style.left = this.characterSize_.width *
945 this.screen_.cursorPosition.column;
946};
947
948/**
949 * Synchronizes the visible cursor with the current cursor coordinates.
950 *
951 * The sync will happen asynchronously, soon after the call stack winds down.
952 * Multiple calls will be coalesced into a single sync.
953 */
954hterm.Terminal.prototype.scheduleSyncCursorPosition_ = function() {
955 if (this.timeouts_.syncCursor)
956 clearTimeout(this.timeouts_.syncCursor);
957
958 var self = this;
959 this.timeouts_.syncCursor = setTimeout(function() {
960 self.syncCursorPosition_();
961 delete self.timeouts_.syncCursor;
962 }, 100);
963};
964
965/**
966 * React when the ScrollPort is scrolled.
967 */
968hterm.Terminal.prototype.onScroll_ = function() {
969 this.scheduleSyncCursorPosition_();
970};
971
972/**
973 * React when the ScrollPort is resized.
974 */
975hterm.Terminal.prototype.onResize_ = function() {
976 var width = Math.floor(this.scrollPort_.getScreenWidth() /
977 this.characterSize_.width);
978 var height = this.scrollPort_.visibleRowCount;
979
980 if (width == this.screenSize.width && height == this.screenSize.height)
981 return;
982
983 this.screenSize.resize(width, height);
984
985 var screenHeight = this.screen_.getHeight();
986
987 var deltaRows = this.screenSize.height - screenHeight;
988
989 if (deltaRows < 0) {
990 // Screen got smaller.
991 var ary = this.screen_.shiftRows(-deltaRows);
992 this.scrollbackRows_.push.apply(this.scrollbackRows_, ary);
993 } else if (deltaRows > 0) {
994 // Screen got larger.
995
996 if (deltaRows <= this.scrollbackRows_.length) {
997 var scrollbackCount = Math.min(deltaRows, this.scrollbackRows_.length);
998 var rows = this.scrollbackRows_.splice(
999 0, this.scrollbackRows_.length - scrollbackCount);
1000 this.screen_.unshiftRows(rows);
1001 deltaRows -= scrollbackCount;
1002 }
1003
1004 if (deltaRows)
1005 this.appendRows_(deltaRows);
1006 }
1007
1008 this.screen_.setColumnCount(this.screenSize.width);
1009
1010 if (this.screen_.cursorPosition.row == -1)
1011 this.screen_.setCursorPosition(0, 0);
1012};
1013
1014/**
1015 * Service the cursor blink timeout.
1016 */
1017hterm.Terminal.prototype.onCursorBlink_ = function() {
1018 if (this.cursorNode_.style.display == 'block') {
1019 this.cursorNode_.style.display = 'none';
1020 } else {
1021 this.cursorNode_.style.display = 'block';
1022 }
1023};