Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(45)

Side by Side Diff: chrome/browser/resources/hterm/js/terminal.js

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

Powered by Google App Engine
This is Rietveld 408576698