blob: 73c97e54d8c0d365b54ad99d925e17dc58a5afd2 [file] [log] [blame]
rginda87b86462011-12-14 13:48:03 -08001// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
rginda8ba33642011-12-14 12:31:31 -08002// 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 */
rginda87b86462011-12-14 13:48:03 -080022hterm.Terminal = function(fontSize, opt_lineHeight) {
rginda8ba33642011-12-14 12:31:31 -080023 // 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
rginda8ba33642011-12-14 12:31:31 -080030 // The local notion of the screen size. ScreenBuffers also have a size which
31 // indicates their present size. During size changes, the two may disagree.
32 // Also, the inactive screen's size is not altered until it is made the active
33 // screen.
34 this.screenSize = new hterm.Size(0, 0);
35
36 // The pixel dimensions of a single character on the screen.
37 this.characterSize_ = new hterm.Size(0, 0);
38
39 // The scroll port we'll be using to display the visible rows.
rginda87b86462011-12-14 13:48:03 -080040 this.scrollPort_ = new hterm.ScrollPort(this, fontSize, opt_lineHeight);
rginda8ba33642011-12-14 12:31:31 -080041 this.scrollPort_.subscribe('resize', this.onResize_.bind(this));
42 this.scrollPort_.subscribe('scroll', this.onScroll_.bind(this));
43
rginda87b86462011-12-14 13:48:03 -080044 // The div that contains this terminal.
45 this.div_ = null;
46
47 // The document that contains the scrollPort. Set in decorate().
48 this.document_ = null;
49
rginda8ba33642011-12-14 12:31:31 -080050 // The rows that have scrolled off screen and are no longer addressable.
51 this.scrollbackRows_ = [];
52
53 // The VT's notion of the top and bottom rows. Used during some VT
54 // cursor positioning and scrolling commands.
55 this.vtScrollTop_ = null;
56 this.vtScrollBottom_ = null;
57
58 // The DIV element for the visible cursor.
59 this.cursorNode_ = null;
60
61 // The default colors for text with no other color attributes.
62 this.backgroundColor = 'black';
63 this.foregroundColor = 'white';
64
65 // The color of the cursor.
66 this.cursorColor = 'rgba(255,0,0,0.5)';
67
rginda87b86462011-12-14 13:48:03 -080068 // If true, scroll to the bottom on any keystroke.
69 this.scrollOnKeystroke = true;
70
rginda8ba33642011-12-14 12:31:31 -080071 // The current mode bits for the terminal.
72 this.options_ = new hterm.Options();
73
74 // Timeouts we might need to clear.
75 this.timeouts_ = {};
rginda87b86462011-12-14 13:48:03 -080076
77 // The VT escape sequence interpreter.
78 this.vt = new hterm.VT100(this);
79
80 // General IO interface that can be given to third parties without exposing
81 // the entire terminal object.
82 this.io = new hterm.Terminal.IO(this);
83};
84
85/**
86 * Create a new instance of a terminal command and run it with a given
87 * argument string.
88 *
89 * @param {function} commandClass The constructor for a terminal command.
90 * @param {string} argString The argument string to pass to the command.
91 */
92hterm.Terminal.prototype.runCommandClass = function(commandClass, argString) {
93 var self = this;
94 this.command = new commandClass(
95 { argString: argString || '',
96 io: this.io.push(),
97 onExit: function(code) {
98 self.io.pop();
99 self.io.println(hterm.msg('COMMAND_COMPLETE',
100 [self.command.commandName, code]));
101 }
102 });
103
104 this.command.run();
105};
106
107/**
108 * Return a copy of the current cursor position.
109 *
110 * @return {hterm.RowCol} The RowCol object representing the current position.
111 */
112hterm.Terminal.prototype.saveCursor = function() {
113 return this.screen_.cursorPosition.clone();
114};
115
116/**
117 * Restore a previously saved cursor position.
118 *
119 * @param {hterm.RowCol} cursor The position to restore.
120 */
121hterm.Terminal.prototype.restoreCursor = function(cursor) {
122 this.screen_.setCursorPosition(cursor.row, cursor.column);
rginda2312fff2012-01-05 16:20:52 -0800123 this.screen_.cursorPosition.overflow = cursor.overflow;
rginda87b86462011-12-14 13:48:03 -0800124};
125
126/**
127 * Set the width of the terminal, resizing the UI to match.
128 */
129hterm.Terminal.prototype.setWidth = function(columnCount) {
130 this.div_.style.width = this.characterSize_.width * columnCount + 16 + 'px'
131
132 // The resizing of the UI will happen asynchronously, so we need to take
133 // care of this bookeeping here instead of letting the resize handlers deal
134 // with it.
135 this.screenSize.width = columnCount;
136 this.screen_.setColumnCount(columnCount);
137};
138
139/**
140 * Scroll the terminal to the top of the scrollback buffer.
141 */
142hterm.Terminal.prototype.scrollHome = function() {
143 this.scrollPort_.scrollRowToTop(0);
144};
145
146/**
147 * Scroll the terminal to the end.
148 */
149hterm.Terminal.prototype.scrollEnd = function() {
150 this.scrollPort_.scrollRowToBottom(this.getRowCount());
151};
152
153/**
154 * Scroll the terminal one page up (minus one line) relative to the current
155 * position.
156 */
157hterm.Terminal.prototype.scrollPageUp = function() {
158 var i = this.scrollPort_.getTopRowIndex();
159 this.scrollPort_.scrollRowToTop(i - this.screenSize.height + 1);
160};
161
162/**
163 * Scroll the terminal one page down (minus one line) relative to the current
164 * position.
165 */
166hterm.Terminal.prototype.scrollPageDown = function() {
167 var i = this.scrollPort_.getTopRowIndex();
168 this.scrollPort_.scrollRowToTop(i + this.screenSize.height - 1);
rginda8ba33642011-12-14 12:31:31 -0800169};
170
171/**
172 * Methods called by Cory's vt100 interpreter which we haven't implemented yet.
173 */
rginda87b86462011-12-14 13:48:03 -0800174hterm.Terminal.prototype.reset = function() {
175 console.log('reset');
176};
177
178hterm.Terminal.prototype.clearColorAndAttributes = function() {
179 //console.log('clearColorAndAttributes');
180};
181
182hterm.Terminal.prototype.setForegroundColor256 = function() {
183 console.log('setForegroundColor256');
184};
185
186hterm.Terminal.prototype.setBackgroundColor256 = function() {
187 console.log('setBackgroundColor256');
188};
189
190hterm.Terminal.prototype.setForegroundColor = function() {
191 //console.log('setForegroundColor');
192};
193
194hterm.Terminal.prototype.setBackgroundColor = function() {
195 //console.log('setBackgroundColor');
196};
197
198hterm.Terminal.prototype.setAttributes = function() {
199 //console.log('setAttributes');
200};
201
202hterm.Terminal.prototype.resize = function() {
203 console.log('resize');
204};
205
206hterm.Terminal.prototype.setSpecialCharsEnabled = function() {
207 //console.log('setSpecialCharactersEnabled');
208};
209
210hterm.Terminal.prototype.setTabStopAtCursor = function() {
211 console.log('setTabStopAtCursor');
212};
213
214hterm.Terminal.prototype.clearTabStops = function() {
215 console.log('clearTabStops');
216};
217
218hterm.Terminal.prototype.saveOptions = function() {
219 console.log('saveOptions');
220};
221
222hterm.Terminal.prototype.restoreOptions = function() {
223 console.log('restoreOptions');
rginda8ba33642011-12-14 12:31:31 -0800224};
225
226/**
227 * Interpret a sequence of characters.
228 *
229 * Incomplete escape sequences are buffered until the next call.
230 *
231 * @param {string} str Sequence of characters to interpret or pass through.
232 */
233hterm.Terminal.prototype.interpret = function(str) {
rginda87b86462011-12-14 13:48:03 -0800234 this.vt.interpretString(str);
rginda8ba33642011-12-14 12:31:31 -0800235 this.scheduleSyncCursorPosition_();
236};
237
238/**
239 * Take over the given DIV for use as the terminal display.
240 *
241 * @param {HTMLDivElement} div The div to use as the terminal display.
242 */
243hterm.Terminal.prototype.decorate = function(div) {
rginda87b86462011-12-14 13:48:03 -0800244 this.div_ = div;
245
rginda8ba33642011-12-14 12:31:31 -0800246 this.scrollPort_.decorate(div);
247 this.document_ = this.scrollPort_.getDocument();
248
249 // Get character dimensions from the scrollPort.
250 this.characterSize_.height = this.scrollPort_.getRowHeight();
251 this.characterSize_.width = this.scrollPort_.getCharacterWidth();
252
253 this.cursorNode_ = this.document_.createElement('div');
254 this.cursorNode_.style.cssText =
255 ('position: absolute;' +
rginda87b86462011-12-14 13:48:03 -0800256 'top: -99px;' +
257 'display: block;' +
rginda8ba33642011-12-14 12:31:31 -0800258 'width: ' + this.characterSize_.width + 'px;' +
259 'height: ' + this.characterSize_.height + 'px;' +
rginda87b86462011-12-14 13:48:03 -0800260 '-webkit-transition: opacity 100ms ease-in;' +
rginda8ba33642011-12-14 12:31:31 -0800261 'background-color: ' + this.cursorColor);
262 this.document_.body.appendChild(this.cursorNode_);
263
264 this.setReverseVideo(false);
rginda87b86462011-12-14 13:48:03 -0800265
266 this.vt.keyboard.installKeyboard(this.document_.body.firstChild);
267
268 var link = this.document_.createElement('link');
269 link.setAttribute('href', '../css/dialogs.css');
270 link.setAttribute('rel', 'stylesheet');
271 this.document_.head.appendChild(link);
272
273 this.alertDialog = new AlertDialog(this.document_.body);
274 this.promptDialog = new PromptDialog(this.document_.body);
275 this.confirmDialog = new ConfirmDialog(this.document_.body);
276
277 this.scrollPort_.focus();
278};
279
280hterm.Terminal.prototype.getDocument = function() {
281 return this.document_;
rginda8ba33642011-12-14 12:31:31 -0800282};
283
284/**
285 * Return the HTML Element for a given row index.
286 *
287 * This is a method from the RowProvider interface. The ScrollPort uses
288 * it to fetch rows on demand as they are scrolled into view.
289 *
290 * TODO(rginda): Consider saving scrollback rows as (HTML source, text content)
291 * pairs to conserve memory.
292 *
293 * @param {integer} index The zero-based row index, measured relative to the
294 * start of the scrollback buffer. On-screen rows will always have the
295 * largest indicies.
296 * @return {HTMLElement} The 'x-row' element containing for the requested row.
297 */
298hterm.Terminal.prototype.getRowNode = function(index) {
299 if (index < this.scrollbackRows_.length)
300 return this.scrollbackRows_[index];
301
302 var screenIndex = index - this.scrollbackRows_.length;
303 return this.screen_.rowsArray[screenIndex];
304};
305
306/**
307 * Return the text content for a given range of rows.
308 *
309 * This is a method from the RowProvider interface. The ScrollPort uses
310 * it to fetch text content on demand when the user attempts to copy their
311 * selection to the clipboard.
312 *
313 * @param {integer} start The zero-based row index to start from, measured
314 * relative to the start of the scrollback buffer. On-screen rows will
315 * always have the largest indicies.
316 * @param {integer} end The zero-based row index to end on, measured
317 * relative to the start of the scrollback buffer.
318 * @return {string} A single string containing the text value of the range of
319 * rows. Lines will be newline delimited, with no trailing newline.
320 */
321hterm.Terminal.prototype.getRowsText = function(start, end) {
322 var ary = [];
323 for (var i = start; i < end; i++) {
324 var node = this.getRowNode(i);
325 ary.push(node.textContent);
326 }
327
328 return ary.join('\n');
329};
330
331/**
332 * Return the text content for a given row.
333 *
334 * This is a method from the RowProvider interface. The ScrollPort uses
335 * it to fetch text content on demand when the user attempts to copy their
336 * selection to the clipboard.
337 *
338 * @param {integer} index The zero-based row index to return, measured
339 * relative to the start of the scrollback buffer. On-screen rows will
340 * always have the largest indicies.
341 * @return {string} A string containing the text value of the selected row.
342 */
343hterm.Terminal.prototype.getRowText = function(index) {
344 var node = this.getRowNode(index);
rginda87b86462011-12-14 13:48:03 -0800345 return node.textContent;
rginda8ba33642011-12-14 12:31:31 -0800346};
347
348/**
349 * Return the total number of rows in the addressable screen and in the
350 * scrollback buffer of this terminal.
351 *
352 * This is a method from the RowProvider interface. The ScrollPort uses
353 * it to compute the size of the scrollbar.
354 *
355 * @return {integer} The number of rows in this terminal.
356 */
357hterm.Terminal.prototype.getRowCount = function() {
358 return this.scrollbackRows_.length + this.screen_.rowsArray.length;
359};
360
361/**
362 * Create DOM nodes for new rows and append them to the end of the terminal.
363 *
364 * This is the only correct way to add a new DOM node for a row. Notice that
365 * the new row is appended to the bottom of the list of rows, and does not
366 * require renumbering (of the rowIndex property) of previous rows.
367 *
368 * If you think you want a new blank row somewhere in the middle of the
369 * terminal, look into moveRows_().
370 *
371 * This method does not pay attention to vtScrollTop/Bottom, since you should
372 * be using moveRows() in cases where they would matter.
373 *
374 * The cursor will be positioned at column 0 of the first inserted line.
375 */
376hterm.Terminal.prototype.appendRows_ = function(count) {
377 var cursorRow = this.screen_.rowsArray.length;
378 var offset = this.scrollbackRows_.length + cursorRow;
379 for (var i = 0; i < count; i++) {
380 var row = this.document_.createElement('x-row');
381 row.appendChild(this.document_.createTextNode(''));
382 row.rowIndex = offset + i;
383 this.screen_.pushRow(row);
384 }
385
386 var extraRows = this.screen_.rowsArray.length - this.screenSize.height;
387 if (extraRows > 0) {
388 var ary = this.screen_.shiftRows(extraRows);
389 Array.prototype.push.apply(this.scrollbackRows_, ary);
390 this.scheduleScrollDown_();
391 }
392
393 if (cursorRow >= this.screen_.rowsArray.length)
394 cursorRow = this.screen_.rowsArray.length - 1;
395
rginda87b86462011-12-14 13:48:03 -0800396 this.setAbsoluteCursorPosition(cursorRow, 0);
rginda8ba33642011-12-14 12:31:31 -0800397};
398
399/**
400 * Relocate rows from one part of the addressable screen to another.
401 *
402 * This is used to recycle rows during VT scrolls (those which are driven
403 * by VT commands, rather than by the user manipulating the scrollbar.)
404 *
405 * In this case, the blank lines scrolled into the scroll region are made of
406 * the nodes we scrolled off. These have their rowIndex properties carefully
407 * renumbered so as not to confuse the ScrollPort.
408 *
409 * TODO(rginda): I'm not sure why this doesn't require a scrollport repaint.
410 * It may just be luck. I wouldn't be surprised if we actually needed to call
411 * scrollPort_.invalidateRowRange, but I'm going to wait for evidence before
412 * adding it.
413 */
414hterm.Terminal.prototype.moveRows_ = function(fromIndex, count, toIndex) {
415 var ary = this.screen_.removeRows(fromIndex, count);
416 this.screen_.insertRows(toIndex, ary);
417
418 var start, end;
419 if (fromIndex < toIndex) {
420 start = fromIndex;
rginda87b86462011-12-14 13:48:03 -0800421 end = toIndex + count;
rginda8ba33642011-12-14 12:31:31 -0800422 } else {
423 start = toIndex;
rginda87b86462011-12-14 13:48:03 -0800424 end = fromIndex + count;
rginda8ba33642011-12-14 12:31:31 -0800425 }
426
427 this.renumberRows_(start, end);
rginda2312fff2012-01-05 16:20:52 -0800428 this.scrollPort_.scheduleInvalidate();
rginda8ba33642011-12-14 12:31:31 -0800429};
430
431/**
432 * Renumber the rowIndex property of the given range of rows.
433 *
434 * The start and end indicies are relative to the screen, not the scrollback.
435 * Rows in the scrollback buffer cannot be renumbered. Since they are not
rginda2312fff2012-01-05 16:20:52 -0800436 * addressable (you can't delete them, scroll them, etc), you should have
rginda8ba33642011-12-14 12:31:31 -0800437 * no need to renumber scrollback rows.
438 */
439hterm.Terminal.prototype.renumberRows_ = function(start, end) {
440 var offset = this.scrollbackRows_.length;
441 for (var i = start; i < end; i++) {
442 this.screen_.rowsArray[i].rowIndex = offset + i;
443 }
444};
445
446/**
447 * Print a string to the terminal.
448 *
449 * This respects the current insert and wraparound modes. It will add new lines
450 * to the end of the terminal, scrolling off the top into the scrollback buffer
451 * if necessary.
452 *
453 * The string is *not* parsed for escape codes. Use the interpret() method if
454 * that's what you're after.
455 *
456 * @param{string} str The string to print.
457 */
458hterm.Terminal.prototype.print = function(str) {
459 do {
rginda2312fff2012-01-05 16:20:52 -0800460 if (this.options_.wraparound && this.screen_.cursorPosition.overflow)
461 this.newLine();
462
rginda8ba33642011-12-14 12:31:31 -0800463 if (this.options_.insertMode) {
464 str = this.screen_.insertString(str);
465 } else {
466 str = this.screen_.overwriteString(str);
467 }
rginda2312fff2012-01-05 16:20:52 -0800468 } while (this.options_.wraparound && str);
rginda8ba33642011-12-14 12:31:31 -0800469
470 this.scheduleSyncCursorPosition_();
471};
472
473/**
rginda87b86462011-12-14 13:48:03 -0800474 * Set the VT scroll region.
475 *
rginda87b86462011-12-14 13:48:03 -0800476 * This also resets the cursor position to the absolute (0, 0) position, since
477 * that's what xterm appears to do.
478 *
479 * @param {integer} scrollTop The zero-based top of the scroll region.
480 * @param {integer} scrollBottom The zero-based bottom of the scroll region,
481 * inclusive.
482 */
483hterm.Terminal.prototype.setVTScrollRegion = function(scrollTop, scrollBottom) {
484 this.vtScrollTop_ = scrollTop;
485 this.vtScrollBottom_ = scrollBottom;
486 this.setAbsoluteCursorPosition(0, 0);
487};
488
489/**
rginda8ba33642011-12-14 12:31:31 -0800490 * Return the top row index according to the VT.
491 *
492 * This will return 0 unless the terminal has been told to restrict scrolling
493 * to some lower row. It is used for some VT cursor positioning and scrolling
494 * commands.
495 *
496 * @return {integer} The topmost row in the terminal's scroll region.
497 */
498hterm.Terminal.prototype.getVTScrollTop = function() {
499 if (this.vtScrollTop_ != null)
500 return this.vtScrollTop_;
501
502 return 0;
rginda87b86462011-12-14 13:48:03 -0800503};
rginda8ba33642011-12-14 12:31:31 -0800504
505/**
506 * Return the bottom row index according to the VT.
507 *
508 * This will return the height of the terminal unless the it has been told to
509 * restrict scrolling to some higher row. It is used for some VT cursor
510 * positioning and scrolling commands.
511 *
512 * @return {integer} The bottommost row in the terminal's scroll region.
513 */
514hterm.Terminal.prototype.getVTScrollBottom = function() {
515 if (this.vtScrollBottom_ != null)
516 return this.vtScrollBottom_;
517
rginda87b86462011-12-14 13:48:03 -0800518 return this.screenSize.height - 1;
rginda8ba33642011-12-14 12:31:31 -0800519}
520
521/**
522 * Process a '\n' character.
523 *
524 * If the cursor is on the final row of the terminal this will append a new
525 * blank row to the screen and scroll the topmost row into the scrollback
526 * buffer.
527 *
528 * Otherwise, this moves the cursor to column zero of the next row.
529 */
530hterm.Terminal.prototype.newLine = function() {
531 if (this.screen_.cursorPosition.row == this.screen_.rowsArray.length - 1) {
rginda87b86462011-12-14 13:48:03 -0800532 // If we're at the end of the screen we need to append a new line and
533 // scroll the top line into the scrollback buffer.
rginda8ba33642011-12-14 12:31:31 -0800534 this.appendRows_(1);
rginda87b86462011-12-14 13:48:03 -0800535 } else if (this.screen_.cursorPosition.row == this.getVTScrollBottom()) {
536 // End of the scroll region does not affect the scrollback buffer.
537 this.vtScrollUp(1);
538 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, 0);
rginda8ba33642011-12-14 12:31:31 -0800539 } else {
rginda87b86462011-12-14 13:48:03 -0800540 // Anywhere else in the screen just moves the cursor.
541 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row + 1, 0);
rginda8ba33642011-12-14 12:31:31 -0800542 }
543};
544
545/**
546 * Like newLine(), except maintain the cursor column.
547 */
548hterm.Terminal.prototype.lineFeed = function() {
549 var column = this.screen_.cursorPosition.column;
550 this.newLine();
551 this.setCursorColumn(column);
552};
553
554/**
rginda87b86462011-12-14 13:48:03 -0800555 * If autoCarriageReturn is set then newLine(), else lineFeed().
556 */
557hterm.Terminal.prototype.formFeed = function() {
558 if (this.options_.autoCarriageReturn) {
559 this.newLine();
560 } else {
561 this.lineFeed();
562 }
563};
564
565/**
566 * Move the cursor up one row, possibly inserting a blank line.
567 *
568 * The cursor column is not changed.
569 */
570hterm.Terminal.prototype.reverseLineFeed = function() {
571 var scrollTop = this.getVTScrollTop();
572 var currentRow = this.screen_.cursorPosition.row;
573
574 if (currentRow == scrollTop) {
575 this.insertLines(1);
576 } else {
577 this.setAbsoluteCursorRow(currentRow - 1);
578 }
579};
580
581/**
rginda8ba33642011-12-14 12:31:31 -0800582 * Replace all characters to the left of the current cursor with the space
583 * character.
584 *
585 * TODO(rginda): This should probably *remove* the characters (not just replace
586 * with a space) if there are no characters at or beyond the current cursor
587 * position. Once it does that, it'll have the same text-attribute related
588 * issues as hterm.Screen.prototype.clearCursorRow :/
589 */
590hterm.Terminal.prototype.eraseToLeft = function() {
rginda87b86462011-12-14 13:48:03 -0800591 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -0800592 this.setCursorColumn(0);
rginda87b86462011-12-14 13:48:03 -0800593 this.screen_.overwriteString(hterm.getWhitespace(cursor.column + 1));
594 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800595};
596
597/**
598 * Erase a given number of characters to the right of the cursor, shifting
599 * remaining characters to the left.
600 *
601 * The cursor position is unchanged.
602 *
603 * TODO(rginda): Test that this works even when the cursor is positioned beyond
604 * the end of the text.
605 *
606 * TODO(rginda): This likely has text-attribute related troubles similar to the
607 * todo on hterm.Screen.prototype.clearCursorRow.
608 */
609hterm.Terminal.prototype.eraseToRight = function(opt_count) {
rginda87b86462011-12-14 13:48:03 -0800610 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -0800611
rginda87b86462011-12-14 13:48:03 -0800612 var maxCount = this.screenSize.width - cursor.column;
rginda8ba33642011-12-14 12:31:31 -0800613 var count = (opt_count && opt_count < maxCount) ? opt_count : maxCount;
614 this.screen_.deleteChars(count);
rginda87b86462011-12-14 13:48:03 -0800615 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800616};
617
618/**
619 * Erase the current line.
620 *
621 * The cursor position is unchanged.
622 *
623 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
624 * has a text-attribute related TODO.
625 */
626hterm.Terminal.prototype.eraseLine = function() {
rginda87b86462011-12-14 13:48:03 -0800627 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -0800628 this.screen_.clearCursorRow();
rginda87b86462011-12-14 13:48:03 -0800629 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800630};
631
632/**
633 * Erase all characters from the start of the scroll region to the current
634 * cursor position.
635 *
636 * The cursor position is unchanged.
637 *
638 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
639 * has a text-attribute related TODO.
640 */
641hterm.Terminal.prototype.eraseAbove = function() {
rginda87b86462011-12-14 13:48:03 -0800642 var cursor = this.saveCursor();
643
644 this.eraseToLeft();
rginda8ba33642011-12-14 12:31:31 -0800645
646 var top = this.getVTScrollTop();
rginda87b86462011-12-14 13:48:03 -0800647 for (var i = top; i < cursor.row; i++) {
648 this.setAbsoluteCursorPosition(i, 0);
rginda8ba33642011-12-14 12:31:31 -0800649 this.screen_.clearCursorRow();
650 }
651
rginda87b86462011-12-14 13:48:03 -0800652 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800653};
654
655/**
656 * Erase all characters from the current cursor position to the end of the
657 * scroll region.
658 *
659 * The cursor position is unchanged.
660 *
661 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
662 * has a text-attribute related TODO.
663 */
664hterm.Terminal.prototype.eraseBelow = function() {
rginda87b86462011-12-14 13:48:03 -0800665 var cursor = this.saveCursor();
666
667 this.eraseToRight();
rginda8ba33642011-12-14 12:31:31 -0800668
669 var bottom = this.getVTScrollBottom();
rginda87b86462011-12-14 13:48:03 -0800670 for (var i = cursor.row + 1; i <= bottom; i++) {
671 this.setAbsoluteCursorPosition(i, 0);
rginda8ba33642011-12-14 12:31:31 -0800672 this.screen_.clearCursorRow();
673 }
674
rginda87b86462011-12-14 13:48:03 -0800675 this.restoreCursor(cursor);
676};
677
678/**
679 * Fill the terminal with a given character.
680 *
681 * This methods does not respect the VT scroll region.
682 *
683 * @param {string} ch The character to use for the fill.
684 */
685hterm.Terminal.prototype.fill = function(ch) {
686 var cursor = this.saveCursor();
687
688 this.setAbsoluteCursorPosition(0, 0);
689 for (var row = 0; row < this.screenSize.height; row++) {
690 for (var col = 0; col < this.screenSize.width; col++) {
691 this.setAbsoluteCursorPosition(row, col);
692 this.screen_.overwriteString(ch);
693 }
694 }
695
696 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800697};
698
699/**
700 * Erase the entire scroll region.
701 *
702 * The cursor position is unchanged.
703 *
704 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
705 * has a text-attribute related TODO.
706 */
707hterm.Terminal.prototype.clear = function() {
rginda87b86462011-12-14 13:48:03 -0800708 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -0800709
710 var top = this.getVTScrollTop();
711 var bottom = this.getVTScrollBottom();
712
713 for (var i = top; i < bottom; i++) {
rginda87b86462011-12-14 13:48:03 -0800714 this.setAbsoluteCursorPosition(i, 0);
rginda8ba33642011-12-14 12:31:31 -0800715 this.screen_.clearCursorRow();
716 }
717
rginda87b86462011-12-14 13:48:03 -0800718 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800719};
720
721/**
722 * VT command to insert lines at the current cursor row.
723 *
724 * This respects the current scroll region. Rows pushed off the bottom are
725 * lost (they won't show up in the scrollback buffer).
726 *
727 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
728 * has a text-attribute related TODO.
729 *
730 * @param {integer} count The number of lines to insert.
731 */
732hterm.Terminal.prototype.insertLines = function(count) {
rginda87b86462011-12-14 13:48:03 -0800733 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -0800734
735 var bottom = this.getVTScrollBottom();
rginda87b86462011-12-14 13:48:03 -0800736 count = Math.min(count, bottom - cursor.row);
rginda8ba33642011-12-14 12:31:31 -0800737
738 var start = bottom - count;
rginda87b86462011-12-14 13:48:03 -0800739 if (start != cursor.row)
740 this.moveRows_(start, count, cursor.row);
rginda8ba33642011-12-14 12:31:31 -0800741
742 for (var i = 0; i < count; i++) {
rginda87b86462011-12-14 13:48:03 -0800743 this.setAbsoluteCursorPosition(cursor.row + i, 0);
rginda8ba33642011-12-14 12:31:31 -0800744 this.screen_.clearCursorRow();
745 }
746
rginda87b86462011-12-14 13:48:03 -0800747 cursor.column = 0;
748 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800749};
750
751/**
752 * VT command to delete lines at the current cursor row.
753 *
754 * New rows are added to the bottom of scroll region to take their place. New
755 * rows are strictly there to take up space and have no content or style.
756 */
757hterm.Terminal.prototype.deleteLines = function(count) {
rginda87b86462011-12-14 13:48:03 -0800758 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -0800759
rginda87b86462011-12-14 13:48:03 -0800760 var top = cursor.row;
rginda8ba33642011-12-14 12:31:31 -0800761 var bottom = this.getVTScrollBottom();
762
rginda87b86462011-12-14 13:48:03 -0800763 var maxCount = bottom - top + 1;
rginda8ba33642011-12-14 12:31:31 -0800764 count = Math.min(count, maxCount);
765
rginda87b86462011-12-14 13:48:03 -0800766 var moveStart = bottom - count + 1;
rginda8ba33642011-12-14 12:31:31 -0800767 if (count != maxCount)
768 this.moveRows_(top, count, moveStart);
769
770 for (var i = 0; i < count; i++) {
rginda87b86462011-12-14 13:48:03 -0800771 this.setAbsoluteCursorPosition(moveStart + i, 0);
rginda8ba33642011-12-14 12:31:31 -0800772 this.screen_.clearCursorRow();
773 }
774
rginda87b86462011-12-14 13:48:03 -0800775 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800776};
777
778/**
779 * Inserts the given number of spaces at the current cursor position.
780 *
rginda87b86462011-12-14 13:48:03 -0800781 * The cursor position is not changed.
rginda8ba33642011-12-14 12:31:31 -0800782 */
783hterm.Terminal.prototype.insertSpace = function(count) {
rginda87b86462011-12-14 13:48:03 -0800784 var cursor = this.saveCursor();
785
rginda8ba33642011-12-14 12:31:31 -0800786 var ws = hterm.getWhitespace(count);
787 this.screen_.insertString(ws);
rginda87b86462011-12-14 13:48:03 -0800788
789 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800790};
791
792/**
793 * Forward-delete the specified number of characters starting at the cursor
794 * position.
795 *
796 * @param {integer} count The number of characters to delete.
797 */
798hterm.Terminal.prototype.deleteChars = function(count) {
799 this.screen_.deleteChars(count);
800};
801
802/**
803 * Shift rows in the scroll region upwards by a given number of lines.
804 *
805 * New rows are inserted at the bottom of the scroll region to fill the
806 * vacated rows. The new rows not filled out with the current text attributes.
807 *
808 * This function does not affect the scrollback rows at all. Rows shifted
809 * off the top are lost.
810 *
rginda87b86462011-12-14 13:48:03 -0800811 * The cursor position is not altered.
812 *
rginda8ba33642011-12-14 12:31:31 -0800813 * @param {integer} count The number of rows to scroll.
814 */
815hterm.Terminal.prototype.vtScrollUp = function(count) {
rginda87b86462011-12-14 13:48:03 -0800816 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -0800817
rginda87b86462011-12-14 13:48:03 -0800818 this.setAbsoluteCursorRow(this.getVTScrollTop());
rginda8ba33642011-12-14 12:31:31 -0800819 this.deleteLines(count);
820
rginda87b86462011-12-14 13:48:03 -0800821 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800822};
823
824/**
825 * Shift rows below the cursor down by a given number of lines.
826 *
827 * This function respects the current scroll region.
828 *
829 * New rows are inserted at the top of the scroll region to fill the
830 * vacated rows. The new rows not filled out with the current text attributes.
831 *
832 * This function does not affect the scrollback rows at all. Rows shifted
833 * off the bottom are lost.
834 *
835 * @param {integer} count The number of rows to scroll.
836 */
837hterm.Terminal.prototype.vtScrollDown = function(opt_count) {
rginda87b86462011-12-14 13:48:03 -0800838 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -0800839
rginda87b86462011-12-14 13:48:03 -0800840 this.setAbsoluteCursorPosition(this.getVTScrollTop(), 0);
rginda8ba33642011-12-14 12:31:31 -0800841 this.insertLines(opt_count);
842
rginda87b86462011-12-14 13:48:03 -0800843 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800844};
845
rginda87b86462011-12-14 13:48:03 -0800846
rginda8ba33642011-12-14 12:31:31 -0800847/**
848 * Set the cursor position.
849 *
850 * The cursor row is relative to the scroll region if the terminal has
851 * 'origin mode' enabled, or relative to the addressable screen otherwise.
852 *
853 * @param {integer} row The new zero-based cursor row.
854 * @param {integer} row The new zero-based cursor column.
855 */
856hterm.Terminal.prototype.setCursorPosition = function(row, column) {
857 if (this.options_.originMode) {
rginda87b86462011-12-14 13:48:03 -0800858 this.setRelativeCursorPosition(row, column);
rginda8ba33642011-12-14 12:31:31 -0800859 } else {
rginda87b86462011-12-14 13:48:03 -0800860 this.setAbsoluteCursorPosition(row, column);
rginda8ba33642011-12-14 12:31:31 -0800861 }
rginda87b86462011-12-14 13:48:03 -0800862};
rginda8ba33642011-12-14 12:31:31 -0800863
rginda87b86462011-12-14 13:48:03 -0800864hterm.Terminal.prototype.setRelativeCursorPosition = function(row, column) {
865 var scrollTop = this.getVTScrollTop();
866 row = hterm.clamp(row + scrollTop, scrollTop, this.getVTScrollBottom());
rginda2312fff2012-01-05 16:20:52 -0800867 column = hterm.clamp(column, 0, this.screenSize.width - 1);
rginda87b86462011-12-14 13:48:03 -0800868 this.screen_.setCursorPosition(row, column);
869};
870
871hterm.Terminal.prototype.setAbsoluteCursorPosition = function(row, column) {
rginda2312fff2012-01-05 16:20:52 -0800872 row = hterm.clamp(row, 0, this.screenSize.height - 1);
873 column = hterm.clamp(column, 0, this.screenSize.width - 1);
rginda8ba33642011-12-14 12:31:31 -0800874 this.screen_.setCursorPosition(row, column);
875};
876
877/**
878 * Set the cursor column.
879 *
880 * @param {integer} column The new zero-based cursor column.
881 */
882hterm.Terminal.prototype.setCursorColumn = function(column) {
rginda87b86462011-12-14 13:48:03 -0800883 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, column);
rginda8ba33642011-12-14 12:31:31 -0800884};
885
886/**
887 * Return the cursor column.
888 *
889 * @return {integer} The zero-based cursor column.
890 */
891hterm.Terminal.prototype.getCursorColumn = function() {
892 return this.screen_.cursorPosition.column;
893};
894
895/**
896 * Set the cursor row.
897 *
898 * The cursor row is relative to the scroll region if the terminal has
899 * 'origin mode' enabled, or relative to the addressable screen otherwise.
900 *
901 * @param {integer} row The new cursor row.
902 */
rginda87b86462011-12-14 13:48:03 -0800903hterm.Terminal.prototype.setAbsoluteCursorRow = function(row) {
904 this.setAbsoluteCursorPosition(row, this.screen_.cursorPosition.column);
rginda8ba33642011-12-14 12:31:31 -0800905};
906
907/**
908 * Return the cursor row.
909 *
910 * @return {integer} The zero-based cursor row.
911 */
912hterm.Terminal.prototype.getCursorRow = function(row) {
913 return this.screen_.cursorPosition.row;
914};
915
916/**
917 * Request that the ScrollPort redraw itself soon.
918 *
919 * The redraw will happen asynchronously, soon after the call stack winds down.
920 * Multiple calls will be coalesced into a single redraw.
921 */
922hterm.Terminal.prototype.scheduleRedraw_ = function() {
rginda87b86462011-12-14 13:48:03 -0800923 if (this.timeouts_.redraw)
924 return;
rginda8ba33642011-12-14 12:31:31 -0800925
926 var self = this;
rginda87b86462011-12-14 13:48:03 -0800927 this.timeouts_.redraw = setTimeout(function() {
928 delete self.timeouts_.redraw;
rginda8ba33642011-12-14 12:31:31 -0800929 self.scrollPort_.redraw_();
930 }, 0);
931};
932
933/**
934 * Request that the ScrollPort be scrolled to the bottom.
935 *
936 * The scroll will happen asynchronously, soon after the call stack winds down.
937 * Multiple calls will be coalesced into a single scroll.
938 *
939 * This affects the scrollbar position of the ScrollPort, and has nothing to
940 * do with the VT scroll commands.
941 */
942hterm.Terminal.prototype.scheduleScrollDown_ = function() {
943 if (this.timeouts_.scrollDown)
rginda87b86462011-12-14 13:48:03 -0800944 return;
rginda8ba33642011-12-14 12:31:31 -0800945
946 var self = this;
947 this.timeouts_.scrollDown = setTimeout(function() {
948 delete self.timeouts_.scrollDown;
949 self.scrollPort_.scrollRowToBottom(self.getRowCount());
950 }, 10);
951};
952
953/**
954 * Move the cursor up a specified number of rows.
955 *
956 * @param {integer} count The number of rows to move the cursor.
957 */
958hterm.Terminal.prototype.cursorUp = function(count) {
959 return this.cursorDown(-count);
960};
961
962/**
963 * Move the cursor down a specified number of rows.
964 *
965 * @param {integer} count The number of rows to move the cursor.
966 */
967hterm.Terminal.prototype.cursorDown = function(count) {
968 var minHeight = (this.options_.originMode ? this.getVTScrollTop() : 0);
969 var maxHeight = (this.options_.originMode ? this.getVTScrollBottom() :
970 this.screenSize.height - 1);
971
972 var row = hterm.clamp(this.screen_.cursorPosition.row + count,
973 minHeight, maxHeight);
rginda87b86462011-12-14 13:48:03 -0800974 this.setAbsoluteCursorRow(row);
rginda8ba33642011-12-14 12:31:31 -0800975};
976
977/**
978 * Move the cursor left a specified number of columns.
979 *
980 * @param {integer} count The number of columns to move the cursor.
981 */
982hterm.Terminal.prototype.cursorLeft = function(count) {
983 return this.cursorRight(-count);
984};
985
986/**
987 * Move the cursor right a specified number of columns.
988 *
989 * @param {integer} count The number of columns to move the cursor.
990 */
991hterm.Terminal.prototype.cursorRight = function(count) {
992 var column = hterm.clamp(this.screen_.cursorPosition.column + count,
rginda87b86462011-12-14 13:48:03 -0800993 0, this.screenSize.width - 1);
rginda8ba33642011-12-14 12:31:31 -0800994 this.setCursorColumn(column);
995};
996
997/**
998 * Reverse the foreground and background colors of the terminal.
999 *
1000 * This only affects text that was drawn with no attributes.
1001 *
1002 * TODO(rginda): Test xterm to see if reverse is respected for text that has
1003 * been drawn with attributes that happen to coincide with the default
1004 * 'no-attribute' colors. My guess is probably not.
1005 */
1006hterm.Terminal.prototype.setReverseVideo = function(state) {
rginda87b86462011-12-14 13:48:03 -08001007 this.options_.reverseVideo = state;
rginda8ba33642011-12-14 12:31:31 -08001008 if (state) {
1009 this.scrollPort_.setForegroundColor(this.backgroundColor);
1010 this.scrollPort_.setBackgroundColor(this.foregroundColor);
1011 } else {
1012 this.scrollPort_.setForegroundColor(this.foregroundColor);
1013 this.scrollPort_.setBackgroundColor(this.backgroundColor);
1014 }
1015};
1016
1017/**
rginda87b86462011-12-14 13:48:03 -08001018 * Ring the terminal bell.
1019 *
1020 * We only have a visual bell, which quickly toggles inverse video in the
1021 * terminal.
1022 */
1023hterm.Terminal.prototype.ringBell = function() {
1024 // We can't toggle using only setReverseVideo, since there's a chance we'll
1025 // get a request to toggle reverse video before our visual bell is over.
1026 var fg = this.scrollPort_.getForegroundColor();
1027 this.scrollPort_.setForegroundColor(this.scrollPort_.getBackgroundColor());
1028 this.scrollPort_.setBackgroundColor(fg);
1029
1030 var self = this;
1031 setTimeout(function() {
1032 self.setReverseVideo(self.options_.reverseVideo);
1033 }, 100);
1034};
1035
1036/**
rginda8ba33642011-12-14 12:31:31 -08001037 * Set the origin mode bit.
1038 *
1039 * If origin mode is on, certain VT cursor and scrolling commands measure their
1040 * row parameter relative to the VT scroll region. Otherwise, row 0 corresponds
1041 * to the top of the addressable screen.
1042 *
1043 * Defaults to off.
1044 *
1045 * @param {boolean} state True to set origin mode, false to unset.
1046 */
1047hterm.Terminal.prototype.setOriginMode = function(state) {
1048 this.options_.originMode = state;
1049};
1050
1051/**
1052 * Set the insert mode bit.
1053 *
1054 * If insert mode is on, existing text beyond the cursor position will be
1055 * shifted right to make room for new text. Otherwise, new text overwrites
1056 * any existing text.
1057 *
1058 * Defaults to off.
1059 *
1060 * @param {boolean} state True to set insert mode, false to unset.
1061 */
1062hterm.Terminal.prototype.setInsertMode = function(state) {
1063 this.options_.insertMode = state;
1064};
1065
1066/**
rginda87b86462011-12-14 13:48:03 -08001067 * Set the auto carriage return bit.
1068 *
1069 * If auto carriage return is on then a formfeed character is interpreted
1070 * as a newline, otherwise it's the same as a linefeed. The difference boils
1071 * down to whether or not the cursor column is reset.
1072 */
1073hterm.Terminal.prototype.setAutoCarriageReturn = function(state) {
1074 this.options_.autoCarriageReturn = state;
1075};
1076
1077/**
rginda8ba33642011-12-14 12:31:31 -08001078 * Set the wraparound mode bit.
1079 *
1080 * If wraparound mode is on, certain VT commands will allow the cursor to wrap
1081 * to the start of the following row. Otherwise, the cursor is clamped to the
1082 * end of the screen and attempts to write past it are ignored.
1083 *
1084 * Defaults to on.
1085 *
1086 * @param {boolean} state True to set wraparound mode, false to unset.
1087 */
1088hterm.Terminal.prototype.setWraparound = function(state) {
1089 this.options_.wraparound = state;
1090};
1091
1092/**
1093 * Set the reverse-wraparound mode bit.
1094 *
1095 * If wraparound mode is off, certain VT commands will allow the cursor to wrap
1096 * to the end of the previous row. Otherwise, the cursor is clamped to column
1097 * 0.
1098 *
1099 * Defaults to off.
1100 *
1101 * @param {boolean} state True to set reverse-wraparound mode, false to unset.
1102 */
1103hterm.Terminal.prototype.setReverseWraparound = function(state) {
1104 this.options_.reverseWraparound = state;
1105};
1106
1107/**
1108 * Selects between the primary and alternate screens.
1109 *
1110 * If alternate mode is on, the alternate screen is active. Otherwise the
1111 * primary screen is active.
1112 *
1113 * Swapping screens has no effect on the scrollback buffer.
1114 *
1115 * Each screen maintains its own cursor position.
1116 *
1117 * Defaults to off.
1118 *
1119 * @param {boolean} state True to set alternate mode, false to unset.
1120 */
1121hterm.Terminal.prototype.setAlternateMode = function(state) {
1122 this.screen_ = state ? this.alternateScreen_ : this.primaryScreen_;
1123
1124 this.screen_.setColumnCount(this.screenSize.width);
1125
1126 var rowDelta = this.screenSize.height - this.screen_.getHeight();
1127 if (rowDelta > 0)
1128 this.appendRows_(rowDelta);
1129
rginda2312fff2012-01-05 16:20:52 -08001130 this.scrollPort_.invalidate();
rginda8ba33642011-12-14 12:31:31 -08001131 this.syncCursorPosition_();
1132};
1133
1134/**
1135 * Set the cursor-blink mode bit.
1136 *
1137 * If cursor-blink is on, the cursor will blink when it is visible. Otherwise
1138 * a visible cursor does not blink.
1139 *
1140 * You should make sure to turn blinking off if you're going to dispose of a
1141 * terminal, otherwise you'll leak a timeout.
1142 *
1143 * Defaults to on.
1144 *
1145 * @param {boolean} state True to set cursor-blink mode, false to unset.
1146 */
1147hterm.Terminal.prototype.setCursorBlink = function(state) {
1148 this.options_.cursorBlink = state;
1149
1150 if (!state && this.timeouts_.cursorBlink) {
1151 clearTimeout(this.timeouts_.cursorBlink);
1152 delete this.timeouts_.cursorBlink;
1153 }
1154
1155 if (this.options_.cursorVisible)
1156 this.setCursorVisible(true);
1157};
1158
1159/**
1160 * Set the cursor-visible mode bit.
1161 *
1162 * If cursor-visible is on, the cursor will be visible. Otherwise it will not.
1163 *
1164 * Defaults to on.
1165 *
1166 * @param {boolean} state True to set cursor-visible mode, false to unset.
1167 */
1168hterm.Terminal.prototype.setCursorVisible = function(state) {
1169 this.options_.cursorVisible = state;
1170
1171 if (!state) {
rginda87b86462011-12-14 13:48:03 -08001172 this.cursorNode_.style.opacity = '0';
rginda8ba33642011-12-14 12:31:31 -08001173 return;
1174 }
1175
rginda87b86462011-12-14 13:48:03 -08001176 this.syncCursorPosition_();
1177
1178 this.cursorNode_.style.opacity = '1';
rginda8ba33642011-12-14 12:31:31 -08001179
1180 if (this.options_.cursorBlink) {
1181 if (this.timeouts_.cursorBlink)
1182 return;
1183
1184 this.timeouts_.cursorBlink = setInterval(this.onCursorBlink_.bind(this),
1185 500);
1186 } else {
1187 if (this.timeouts_.cursorBlink) {
1188 clearTimeout(this.timeouts_.cursorBlink);
1189 delete this.timeouts_.cursorBlink;
1190 }
1191 }
1192};
1193
1194/**
rginda87b86462011-12-14 13:48:03 -08001195 * Synchronizes the visible cursor and document selection with the current
1196 * cursor coordinates.
rginda8ba33642011-12-14 12:31:31 -08001197 */
1198hterm.Terminal.prototype.syncCursorPosition_ = function() {
1199 var topRowIndex = this.scrollPort_.getTopRowIndex();
1200 var bottomRowIndex = this.scrollPort_.getBottomRowIndex(topRowIndex);
1201 var cursorRowIndex = this.scrollbackRows_.length +
1202 this.screen_.cursorPosition.row;
1203
1204 if (cursorRowIndex > bottomRowIndex) {
1205 // Cursor is scrolled off screen, move it outside of the visible area.
1206 this.cursorNode_.style.top = -this.characterSize_.height;
1207 return;
1208 }
1209
1210 this.cursorNode_.style.top = this.scrollPort_.visibleRowTopMargin +
1211 this.characterSize_.height * (cursorRowIndex - topRowIndex);
1212 this.cursorNode_.style.left = this.characterSize_.width *
1213 this.screen_.cursorPosition.column;
rginda87b86462011-12-14 13:48:03 -08001214
1215 this.cursorNode_.setAttribute('title',
1216 '(' + this.screen_.cursorPosition.row +
1217 ', ' + this.screen_.cursorPosition.column +
1218 ')');
1219
1220 // Update the caret for a11y purposes.
1221 var selection = this.document_.getSelection();
1222 if (selection && selection.isCollapsed)
1223 this.screen_.syncSelectionCaret(selection);
rginda8ba33642011-12-14 12:31:31 -08001224};
1225
1226/**
1227 * Synchronizes the visible cursor with the current cursor coordinates.
1228 *
1229 * The sync will happen asynchronously, soon after the call stack winds down.
1230 * Multiple calls will be coalesced into a single sync.
1231 */
1232hterm.Terminal.prototype.scheduleSyncCursorPosition_ = function() {
1233 if (this.timeouts_.syncCursor)
rginda87b86462011-12-14 13:48:03 -08001234 return;
rginda8ba33642011-12-14 12:31:31 -08001235
1236 var self = this;
1237 this.timeouts_.syncCursor = setTimeout(function() {
1238 self.syncCursorPosition_();
1239 delete self.timeouts_.syncCursor;
rginda87b86462011-12-14 13:48:03 -08001240 }, 0);
1241};
1242
1243/**
1244 * Invoked by hterm.Terminal.Keyboard when a VT keystroke is detected.
1245 *
1246 * @param {string} string The VT string representing the keystroke.
1247 */
1248hterm.Terminal.prototype.onVTKeystroke = function(string) {
1249 if (this.scrollOnKeystroke)
1250 this.scrollPort_.scrollRowToBottom(this.getRowCount());
1251
1252 this.io.onVTKeystroke(string);
rginda8ba33642011-12-14 12:31:31 -08001253};
1254
1255/**
1256 * React when the ScrollPort is scrolled.
1257 */
1258hterm.Terminal.prototype.onScroll_ = function() {
1259 this.scheduleSyncCursorPosition_();
1260};
1261
1262/**
1263 * React when the ScrollPort is resized.
1264 */
1265hterm.Terminal.prototype.onResize_ = function() {
1266 var width = Math.floor(this.scrollPort_.getScreenWidth() /
1267 this.characterSize_.width);
1268 var height = this.scrollPort_.visibleRowCount;
1269
rginda87b86462011-12-14 13:48:03 -08001270 if (width == this.screenSize.width && height == this.screenSize.height) {
1271 this.syncCursorPosition_();
rginda8ba33642011-12-14 12:31:31 -08001272 return;
rginda87b86462011-12-14 13:48:03 -08001273 }
rginda8ba33642011-12-14 12:31:31 -08001274
1275 this.screenSize.resize(width, height);
1276
1277 var screenHeight = this.screen_.getHeight();
1278
1279 var deltaRows = this.screenSize.height - screenHeight;
1280
rginda87b86462011-12-14 13:48:03 -08001281 var cursor = this.saveCursor();
1282
rginda8ba33642011-12-14 12:31:31 -08001283 if (deltaRows < 0) {
1284 // Screen got smaller.
rginda87b86462011-12-14 13:48:03 -08001285 deltaRows *= -1;
1286 while (deltaRows) {
1287 var lastRow = this.getRowCount() - 1;
1288 if (lastRow - this.scrollbackRows_.length == cursor.row)
1289 break;
1290
1291 if (this.getRowText(lastRow))
1292 break;
1293
1294 this.screen_.popRow();
1295 deltaRows--;
1296 }
1297
1298 var ary = this.screen_.shiftRows(deltaRows);
rginda8ba33642011-12-14 12:31:31 -08001299 this.scrollbackRows_.push.apply(this.scrollbackRows_, ary);
rginda87b86462011-12-14 13:48:03 -08001300
1301 // We just removed rows from the top of the screen, we need to update
1302 // the cursor to match.
1303 cursor.row -= deltaRows;
1304
rginda8ba33642011-12-14 12:31:31 -08001305 } else if (deltaRows > 0) {
1306 // Screen got larger.
1307
1308 if (deltaRows <= this.scrollbackRows_.length) {
1309 var scrollbackCount = Math.min(deltaRows, this.scrollbackRows_.length);
1310 var rows = this.scrollbackRows_.splice(
rginda87b86462011-12-14 13:48:03 -08001311 this.scrollbackRows_.length - scrollbackCount, scrollbackCount);
rginda8ba33642011-12-14 12:31:31 -08001312 this.screen_.unshiftRows(rows);
1313 deltaRows -= scrollbackCount;
rginda87b86462011-12-14 13:48:03 -08001314 cursor.row += scrollbackCount;
rginda8ba33642011-12-14 12:31:31 -08001315 }
1316
1317 if (deltaRows)
1318 this.appendRows_(deltaRows);
1319 }
1320
1321 this.screen_.setColumnCount(this.screenSize.width);
rginda87b86462011-12-14 13:48:03 -08001322 this.restoreCursor(cursor);
1323 this.syncCursorPosition_();
rginda8ba33642011-12-14 12:31:31 -08001324};
1325
1326/**
1327 * Service the cursor blink timeout.
1328 */
1329hterm.Terminal.prototype.onCursorBlink_ = function() {
rginda87b86462011-12-14 13:48:03 -08001330 if (this.cursorNode_.style.opacity == '0') {
1331 this.cursorNode_.style.opacity = '1';
rginda8ba33642011-12-14 12:31:31 -08001332 } else {
rginda87b86462011-12-14 13:48:03 -08001333 this.cursorNode_.style.opacity = '0';
rginda8ba33642011-12-14 12:31:31 -08001334 }
1335};