blob: 558dd07c494e09a6bb493d12d9208fa180961500 [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));
rginda9846e2f2012-01-27 13:53:33 -080043 this.scrollPort_.subscribe('paste', this.onPaste_.bind(this));
rginda8ba33642011-12-14 12:31:31 -080044
rginda87b86462011-12-14 13:48:03 -080045 // The div that contains this terminal.
46 this.div_ = null;
47
rgindac9bc5502012-01-18 11:48:44 -080048 // The document that contains the scrollPort. Defaulted to the global
49 // document here so that the terminal is functional even if it hasn't been
50 // inserted into a document yet, but re-set in decorate().
51 this.document_ = window.document;
rginda87b86462011-12-14 13:48:03 -080052
rginda8ba33642011-12-14 12:31:31 -080053 // The rows that have scrolled off screen and are no longer addressable.
54 this.scrollbackRows_ = [];
55
rgindac9bc5502012-01-18 11:48:44 -080056 // Saved tab stops.
57 this.tabStops_ = [];
58
rginda8ba33642011-12-14 12:31:31 -080059 // The VT's notion of the top and bottom rows. Used during some VT
60 // cursor positioning and scrolling commands.
61 this.vtScrollTop_ = null;
62 this.vtScrollBottom_ = null;
63
64 // The DIV element for the visible cursor.
65 this.cursorNode_ = null;
66
rgindaa19afe22012-01-25 15:40:22 -080067 // Default font family for the terminal text.
68 this.defaultFontFamily_ = '"DejaVu Sans Mono", "Everson Mono", FreeMono, ' +
69 '"Andale Mono", "Lucida Console", monospace'
70
rginda8ba33642011-12-14 12:31:31 -080071 // The default colors for text with no other color attributes.
72 this.backgroundColor = 'black';
73 this.foregroundColor = 'white';
74
rgindac9bc5502012-01-18 11:48:44 -080075 // Default tab with of 8 to match xterm.
76 this.tabWidth = 8;
77
rgindaa19afe22012-01-25 15:40:22 -080078 // The color of the visible cursor.
rginda8ba33642011-12-14 12:31:31 -080079 this.cursorColor = 'rgba(255,0,0,0.5)';
80
rginda87b86462011-12-14 13:48:03 -080081 // If true, scroll to the bottom on any keystroke.
82 this.scrollOnKeystroke = true;
83
rginda0f5c0292012-01-13 11:00:13 -080084 // If true, scroll to the bottom on terminal output.
85 this.scrollOnOutput = false;
86
rginda6d397402012-01-17 10:58:29 -080087 // Cursor position and attributes saved with DECSC.
88 this.savedOptions_ = {};
89
rginda8ba33642011-12-14 12:31:31 -080090 // The current mode bits for the terminal.
91 this.options_ = new hterm.Options();
92
93 // Timeouts we might need to clear.
94 this.timeouts_ = {};
rginda87b86462011-12-14 13:48:03 -080095
96 // The VT escape sequence interpreter.
rginda0f5c0292012-01-13 11:00:13 -080097 this.vt = new hterm.VT(this);
rginda87b86462011-12-14 13:48:03 -080098
rgindafeaf3142012-01-31 15:14:20 -080099 // The keyboard hander.
100 this.keyboard = new hterm.Keyboard(this);
101
rginda87b86462011-12-14 13:48:03 -0800102 // General IO interface that can be given to third parties without exposing
103 // the entire terminal object.
104 this.io = new hterm.Terminal.IO(this);
rgindac9bc5502012-01-18 11:48:44 -0800105
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +0400106 this.realizeSize_(80, 24);
rgindac9bc5502012-01-18 11:48:44 -0800107 this.setDefaultTabStops();
rginda87b86462011-12-14 13:48:03 -0800108};
109
110/**
111 * Create a new instance of a terminal command and run it with a given
112 * argument string.
113 *
114 * @param {function} commandClass The constructor for a terminal command.
115 * @param {string} argString The argument string to pass to the command.
116 */
117hterm.Terminal.prototype.runCommandClass = function(commandClass, argString) {
118 var self = this;
119 this.command = new commandClass(
120 { argString: argString || '',
121 io: this.io.push(),
122 onExit: function(code) {
123 self.io.pop();
124 self.io.println(hterm.msg('COMMAND_COMPLETE',
125 [self.command.commandName, code]));
rgindafeaf3142012-01-31 15:14:20 -0800126 self.uninstallKeyboard();
rginda87b86462011-12-14 13:48:03 -0800127 }
128 });
129
rgindafeaf3142012-01-31 15:14:20 -0800130 this.installKeyboard();
rginda87b86462011-12-14 13:48:03 -0800131 this.command.run();
132};
133
134/**
rgindafeaf3142012-01-31 15:14:20 -0800135 * Returns true if the current screen is the primary screen, false otherwise.
136 */
137hterm.Terminal.prototype.isPrimaryScreen = function() {
138 return this.screen_ = this.primaryScreen_;
139};
140
141/**
142 * Install the keyboard handler for this terminal.
143 *
144 * This will prevent the browser from seeing any keystrokes sent to the
145 * terminal.
146 */
147hterm.Terminal.prototype.installKeyboard = function() {
148 this.keyboard.installKeyboard(this.document_.body.firstChild);
149}
150
151/**
152 * Uninstall the keyboard handler for this terminal.
153 */
154hterm.Terminal.prototype.uninstallKeyboard = function() {
155 this.keyboard.installKeyboard(null);
156}
157
158/**
rginda87b86462011-12-14 13:48:03 -0800159 * Return a copy of the current cursor position.
160 *
161 * @return {hterm.RowCol} The RowCol object representing the current position.
162 */
163hterm.Terminal.prototype.saveCursor = function() {
164 return this.screen_.cursorPosition.clone();
165};
166
rgindaa19afe22012-01-25 15:40:22 -0800167hterm.Terminal.prototype.getTextAttributes = function() {
168 return this.screen_.textAttributes;
169};
170
rginda87b86462011-12-14 13:48:03 -0800171/**
rginda9846e2f2012-01-27 13:53:33 -0800172 * Change the title of this terminal's window.
173 */
174hterm.Terminal.prototype.setWindowTitle = function(title) {
rgindafeaf3142012-01-31 15:14:20 -0800175 window.document.title = title;
rginda9846e2f2012-01-27 13:53:33 -0800176};
177
178/**
rginda87b86462011-12-14 13:48:03 -0800179 * Restore a previously saved cursor position.
180 *
181 * @param {hterm.RowCol} cursor The position to restore.
182 */
183hterm.Terminal.prototype.restoreCursor = function(cursor) {
184 this.screen_.setCursorPosition(cursor.row, cursor.column);
rginda2312fff2012-01-05 16:20:52 -0800185 this.screen_.cursorPosition.overflow = cursor.overflow;
rginda87b86462011-12-14 13:48:03 -0800186};
187
188/**
189 * Set the width of the terminal, resizing the UI to match.
190 */
191hterm.Terminal.prototype.setWidth = function(columnCount) {
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +0400192 this.div_.style.width = this.characterSize_.width * columnCount + 16 + 'px';
193 this.realizeSize_(columnCount, this.screenSize.height);
rgindac9bc5502012-01-18 11:48:44 -0800194 this.scheduleSyncCursorPosition_();
195};
rginda87b86462011-12-14 13:48:03 -0800196
rgindac9bc5502012-01-18 11:48:44 -0800197/**
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +0400198 * Deal with terminal size changes.
199 *
200 */
201hterm.Terminal.prototype.realizeSize_ = function(columnCount, rowCount) {
202 if (columnCount != this.screenSize.width)
203 this.realizeWidth_(columnCount);
204
205 if (rowCount != this.screenSize.height)
206 this.realizeHeight_(rowCount);
207
208 // Send new terminal size to plugin.
209 this.io.onTerminalResize(columnCount, rowCount);
210};
211
212/**
rgindac9bc5502012-01-18 11:48:44 -0800213 * Deal with terminal width changes.
214 *
215 * This function does what needs to be done when the terminal width changes
216 * out from under us. It happens here rather than in onResize_() because this
217 * code may need to run synchronously to handle programmatic changes of
218 * terminal width.
219 *
220 * Relying on the browser to send us an async resize event means we may not be
221 * in the correct state yet when the next escape sequence hits.
222 */
223hterm.Terminal.prototype.realizeWidth_ = function(columnCount) {
224 var deltaColumns = columnCount - this.screen_.getWidth();
225
rginda87b86462011-12-14 13:48:03 -0800226 this.screenSize.width = columnCount;
227 this.screen_.setColumnCount(columnCount);
rgindac9bc5502012-01-18 11:48:44 -0800228
229 if (deltaColumns > 0) {
230 this.setDefaultTabStops(this.screenSize.width - deltaColumns);
231 } else {
232 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
233 if (this.tabStops_[i] <= columnCount)
234 break;
235
236 this.tabStops_.pop();
237 }
238 }
239
240 this.screen_.setColumnCount(this.screenSize.width);
241};
242
243/**
244 * Deal with terminal height changes.
245 *
246 * This function does what needs to be done when the terminal height changes
247 * out from under us. It happens here rather than in onResize_() because this
248 * code may need to run synchronously to handle programmatic changes of
249 * terminal height.
250 *
251 * Relying on the browser to send us an async resize event means we may not be
252 * in the correct state yet when the next escape sequence hits.
253 */
254hterm.Terminal.prototype.realizeHeight_ = function(rowCount) {
255 var deltaRows = rowCount - this.screen_.getHeight();
256
257 this.screenSize.height = rowCount;
258
259 var cursor = this.saveCursor();
260
261 if (deltaRows < 0) {
262 // Screen got smaller.
263 deltaRows *= -1;
264 while (deltaRows) {
265 var lastRow = this.getRowCount() - 1;
266 if (lastRow - this.scrollbackRows_.length == cursor.row)
267 break;
268
269 if (this.getRowText(lastRow))
270 break;
271
272 this.screen_.popRow();
273 deltaRows--;
274 }
275
276 var ary = this.screen_.shiftRows(deltaRows);
277 this.scrollbackRows_.push.apply(this.scrollbackRows_, ary);
278
279 // We just removed rows from the top of the screen, we need to update
280 // the cursor to match.
281 cursor.row -= deltaRows;
282
283 } else if (deltaRows > 0) {
284 // Screen got larger.
285
286 if (deltaRows <= this.scrollbackRows_.length) {
287 var scrollbackCount = Math.min(deltaRows, this.scrollbackRows_.length);
288 var rows = this.scrollbackRows_.splice(
289 this.scrollbackRows_.length - scrollbackCount, scrollbackCount);
290 this.screen_.unshiftRows(rows);
291 deltaRows -= scrollbackCount;
292 cursor.row += scrollbackCount;
293 }
294
295 if (deltaRows)
296 this.appendRows_(deltaRows);
297 }
298
299 this.restoreCursor(cursor);
rginda87b86462011-12-14 13:48:03 -0800300};
301
302/**
303 * Scroll the terminal to the top of the scrollback buffer.
304 */
305hterm.Terminal.prototype.scrollHome = function() {
306 this.scrollPort_.scrollRowToTop(0);
307};
308
309/**
310 * Scroll the terminal to the end.
311 */
312hterm.Terminal.prototype.scrollEnd = function() {
313 this.scrollPort_.scrollRowToBottom(this.getRowCount());
314};
315
316/**
317 * Scroll the terminal one page up (minus one line) relative to the current
318 * position.
319 */
320hterm.Terminal.prototype.scrollPageUp = function() {
321 var i = this.scrollPort_.getTopRowIndex();
322 this.scrollPort_.scrollRowToTop(i - this.screenSize.height + 1);
323};
324
325/**
326 * Scroll the terminal one page down (minus one line) relative to the current
327 * position.
328 */
329hterm.Terminal.prototype.scrollPageDown = function() {
330 var i = this.scrollPort_.getTopRowIndex();
331 this.scrollPort_.scrollRowToTop(i + this.screenSize.height - 1);
rginda8ba33642011-12-14 12:31:31 -0800332};
333
rgindac9bc5502012-01-18 11:48:44 -0800334/**
335 * Full terminal reset.
336 */
rginda87b86462011-12-14 13:48:03 -0800337hterm.Terminal.prototype.reset = function() {
rgindac9bc5502012-01-18 11:48:44 -0800338 this.clearAllTabStops();
339 this.setDefaultTabStops();
340 this.clearColorAndAttributes();
341 this.setVTScrollRegion(null, null);
342 this.clear();
343 this.setAbsoluteCursorPosition(0, 0);
344 this.softReset();
rginda87b86462011-12-14 13:48:03 -0800345};
346
rgindac9bc5502012-01-18 11:48:44 -0800347/**
348 * Soft terminal reset.
349 */
rginda0f5c0292012-01-13 11:00:13 -0800350hterm.Terminal.prototype.softReset = function() {
rgindac9bc5502012-01-18 11:48:44 -0800351 this.options_ = new hterm.Options();
rgindaa19afe22012-01-25 15:40:22 -0800352 this.setCursorVisible(true);
353 this.setCursorBlink(false);
rginda0f5c0292012-01-13 11:00:13 -0800354};
355
rginda87b86462011-12-14 13:48:03 -0800356hterm.Terminal.prototype.clearColorAndAttributes = function() {
357 //console.log('clearColorAndAttributes');
358};
359
360hterm.Terminal.prototype.setForegroundColor256 = function() {
361 console.log('setForegroundColor256');
362};
363
364hterm.Terminal.prototype.setBackgroundColor256 = function() {
365 console.log('setBackgroundColor256');
366};
367
368hterm.Terminal.prototype.setForegroundColor = function() {
369 //console.log('setForegroundColor');
370};
371
372hterm.Terminal.prototype.setBackgroundColor = function() {
373 //console.log('setBackgroundColor');
374};
375
376hterm.Terminal.prototype.setAttributes = function() {
377 //console.log('setAttributes');
378};
379
380hterm.Terminal.prototype.resize = function() {
381 console.log('resize');
382};
383
rgindae4d29232012-01-19 10:47:13 -0800384hterm.Terminal.prototype.setCharacterSet = function() {
385 //console.log('setCharacterSet');
rginda87b86462011-12-14 13:48:03 -0800386};
387
rgindac9bc5502012-01-18 11:48:44 -0800388/**
389 * Move the cursor forward to the next tab stop, or to the last column
390 * if no more tab stops are set.
391 */
392hterm.Terminal.prototype.forwardTabStop = function() {
393 var column = this.screen_.cursorPosition.column;
394
395 for (var i = 0; i < this.tabStops_.length; i++) {
396 if (this.tabStops_[i] > column) {
397 this.setCursorColumn(this.tabStops_[i]);
398 return;
399 }
400 }
401
402 this.setCursorColumn(this.screenSize.width - 1);
rginda0f5c0292012-01-13 11:00:13 -0800403};
404
rgindac9bc5502012-01-18 11:48:44 -0800405/**
406 * Move the cursor backward to the previous tab stop, or to the first column
407 * if no previous tab stops are set.
408 */
409hterm.Terminal.prototype.backwardTabStop = function() {
410 var column = this.screen_.cursorPosition.column;
411
412 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
413 if (this.tabStops_[i] < column) {
414 this.setCursorColumn(this.tabStops_[i]);
415 return;
416 }
417 }
418
419 this.setCursorColumn(1);
rginda0f5c0292012-01-13 11:00:13 -0800420};
421
rgindac9bc5502012-01-18 11:48:44 -0800422/**
423 * Set a tab stop at the given column.
424 *
425 * @param {int} column Zero based column.
426 */
427hterm.Terminal.prototype.setTabStop = function(column) {
428 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
429 if (this.tabStops_[i] == column)
430 return;
431
432 if (this.tabStops_[i] < column) {
433 this.tabStops_.splice(i + 1, 0, column);
434 return;
435 }
436 }
437
438 this.tabStops_.splice(0, 0, column);
rginda87b86462011-12-14 13:48:03 -0800439};
440
rgindac9bc5502012-01-18 11:48:44 -0800441/**
442 * Clear the tab stop at the current cursor position.
443 *
444 * No effect if there is no tab stop at the current cursor position.
445 */
446hterm.Terminal.prototype.clearTabStopAtCursor = function() {
447 var column = this.screen_.cursorPosition.column;
448
449 var i = this.tabStops_.indexOf(column);
450 if (i == -1)
451 return;
452
453 this.tabStops_.splice(i, 1);
454};
455
456/**
457 * Clear all tab stops.
458 */
459hterm.Terminal.prototype.clearAllTabStops = function() {
460 this.tabStops_.length = 0;
461};
462
463/**
464 * Set up the default tab stops, starting from a given column.
465 *
466 * This sets a tabstop every (column % this.tabWidth) column, starting
467 * from the specified column, or 0 if no column is provided.
468 *
469 * This does not clear the existing tab stops first, use clearAllTabStops
470 * for that.
471 *
472 * @param {int} opt_start Optional starting zero based starting column, useful
473 * for filling out missing tab stops when the terminal is resized.
474 */
475hterm.Terminal.prototype.setDefaultTabStops = function(opt_start) {
476 var start = opt_start || 0;
477 var w = this.tabWidth;
478 var stopCount = Math.floor((this.screenSize.width - start) / this.tabWidth)
479 for (var i = 0; i < stopCount; i++) {
480 this.setTabStop(Math.floor((start + i * w) / w) * w + w);
481 }
rginda87b86462011-12-14 13:48:03 -0800482};
483
rginda6d397402012-01-17 10:58:29 -0800484/**
485 * Save cursor position and attributes.
486 *
487 * TODO(rginda): Save attributes once we support them.
488 */
rginda87b86462011-12-14 13:48:03 -0800489hterm.Terminal.prototype.saveOptions = function() {
rginda6d397402012-01-17 10:58:29 -0800490 this.savedOptions_.cursor = this.saveCursor();
rgindaa19afe22012-01-25 15:40:22 -0800491 this.savedOptions_.textAttributes = this.screen_.textAttributes.clone();
rginda87b86462011-12-14 13:48:03 -0800492};
493
rginda6d397402012-01-17 10:58:29 -0800494/**
495 * Restore cursor position and attributes.
496 *
497 * TODO(rginda): Restore attributes once we support them.
498 */
rginda87b86462011-12-14 13:48:03 -0800499hterm.Terminal.prototype.restoreOptions = function() {
rginda6d397402012-01-17 10:58:29 -0800500 if (this.savedOptions_.cursor)
501 this.restoreCursor(this.savedOptions_.cursor);
rgindaa19afe22012-01-25 15:40:22 -0800502 if (this.savedOptions_.textAttributes)
503 this.screen_.textAttributes = this.savedOptions_.textAttributes;
rginda8ba33642011-12-14 12:31:31 -0800504};
505
506/**
507 * Interpret a sequence of characters.
508 *
509 * Incomplete escape sequences are buffered until the next call.
510 *
511 * @param {string} str Sequence of characters to interpret or pass through.
512 */
513hterm.Terminal.prototype.interpret = function(str) {
rginda0f5c0292012-01-13 11:00:13 -0800514 this.vt.interpret(str);
rginda8ba33642011-12-14 12:31:31 -0800515 this.scheduleSyncCursorPosition_();
516};
517
518/**
519 * Take over the given DIV for use as the terminal display.
520 *
521 * @param {HTMLDivElement} div The div to use as the terminal display.
522 */
523hterm.Terminal.prototype.decorate = function(div) {
rginda87b86462011-12-14 13:48:03 -0800524 this.div_ = div;
525
rginda8ba33642011-12-14 12:31:31 -0800526 this.scrollPort_.decorate(div);
rgindaa19afe22012-01-25 15:40:22 -0800527 this.scrollPort_.setFontFamily(this.defaultFontFamily_);
528
rginda8ba33642011-12-14 12:31:31 -0800529 this.document_ = this.scrollPort_.getDocument();
530
531 // Get character dimensions from the scrollPort.
532 this.characterSize_.height = this.scrollPort_.getRowHeight();
533 this.characterSize_.width = this.scrollPort_.getCharacterWidth();
534
535 this.cursorNode_ = this.document_.createElement('div');
536 this.cursorNode_.style.cssText =
537 ('position: absolute;' +
rginda87b86462011-12-14 13:48:03 -0800538 'top: -99px;' +
539 'display: block;' +
rginda8ba33642011-12-14 12:31:31 -0800540 'width: ' + this.characterSize_.width + 'px;' +
541 'height: ' + this.characterSize_.height + 'px;' +
rginda6d397402012-01-17 10:58:29 -0800542 '-webkit-transition: opacity, background-color 100ms linear;' +
rginda8ba33642011-12-14 12:31:31 -0800543 'background-color: ' + this.cursorColor);
544 this.document_.body.appendChild(this.cursorNode_);
545
546 this.setReverseVideo(false);
rginda87b86462011-12-14 13:48:03 -0800547
rginda87b86462011-12-14 13:48:03 -0800548 var link = this.document_.createElement('link');
549 link.setAttribute('href', '../css/dialogs.css');
550 link.setAttribute('rel', 'stylesheet');
551 this.document_.head.appendChild(link);
552
553 this.alertDialog = new AlertDialog(this.document_.body);
554 this.promptDialog = new PromptDialog(this.document_.body);
555 this.confirmDialog = new ConfirmDialog(this.document_.body);
556
557 this.scrollPort_.focus();
rginda6d397402012-01-17 10:58:29 -0800558 this.scrollPort_.scheduleRedraw();
rginda87b86462011-12-14 13:48:03 -0800559};
560
561hterm.Terminal.prototype.getDocument = function() {
562 return this.document_;
rginda8ba33642011-12-14 12:31:31 -0800563};
564
565/**
566 * Return the HTML Element for a given row index.
567 *
568 * This is a method from the RowProvider interface. The ScrollPort uses
569 * it to fetch rows on demand as they are scrolled into view.
570 *
571 * TODO(rginda): Consider saving scrollback rows as (HTML source, text content)
572 * pairs to conserve memory.
573 *
574 * @param {integer} index The zero-based row index, measured relative to the
575 * start of the scrollback buffer. On-screen rows will always have the
576 * largest indicies.
577 * @return {HTMLElement} The 'x-row' element containing for the requested row.
578 */
579hterm.Terminal.prototype.getRowNode = function(index) {
580 if (index < this.scrollbackRows_.length)
581 return this.scrollbackRows_[index];
582
583 var screenIndex = index - this.scrollbackRows_.length;
584 return this.screen_.rowsArray[screenIndex];
585};
586
587/**
588 * Return the text content for a given range of rows.
589 *
590 * This is a method from the RowProvider interface. The ScrollPort uses
591 * it to fetch text content on demand when the user attempts to copy their
592 * selection to the clipboard.
593 *
594 * @param {integer} start The zero-based row index to start from, measured
595 * relative to the start of the scrollback buffer. On-screen rows will
596 * always have the largest indicies.
597 * @param {integer} end The zero-based row index to end on, measured
598 * relative to the start of the scrollback buffer.
599 * @return {string} A single string containing the text value of the range of
600 * rows. Lines will be newline delimited, with no trailing newline.
601 */
602hterm.Terminal.prototype.getRowsText = function(start, end) {
603 var ary = [];
604 for (var i = start; i < end; i++) {
605 var node = this.getRowNode(i);
606 ary.push(node.textContent);
607 }
608
609 return ary.join('\n');
610};
611
612/**
613 * Return the text content for a given row.
614 *
615 * This is a method from the RowProvider interface. The ScrollPort uses
616 * it to fetch text content on demand when the user attempts to copy their
617 * selection to the clipboard.
618 *
619 * @param {integer} index The zero-based row index to return, measured
620 * relative to the start of the scrollback buffer. On-screen rows will
621 * always have the largest indicies.
622 * @return {string} A string containing the text value of the selected row.
623 */
624hterm.Terminal.prototype.getRowText = function(index) {
625 var node = this.getRowNode(index);
rginda87b86462011-12-14 13:48:03 -0800626 return node.textContent;
rginda8ba33642011-12-14 12:31:31 -0800627};
628
629/**
630 * Return the total number of rows in the addressable screen and in the
631 * scrollback buffer of this terminal.
632 *
633 * This is a method from the RowProvider interface. The ScrollPort uses
634 * it to compute the size of the scrollbar.
635 *
636 * @return {integer} The number of rows in this terminal.
637 */
638hterm.Terminal.prototype.getRowCount = function() {
639 return this.scrollbackRows_.length + this.screen_.rowsArray.length;
640};
641
642/**
643 * Create DOM nodes for new rows and append them to the end of the terminal.
644 *
645 * This is the only correct way to add a new DOM node for a row. Notice that
646 * the new row is appended to the bottom of the list of rows, and does not
647 * require renumbering (of the rowIndex property) of previous rows.
648 *
649 * If you think you want a new blank row somewhere in the middle of the
650 * terminal, look into moveRows_().
651 *
652 * This method does not pay attention to vtScrollTop/Bottom, since you should
653 * be using moveRows() in cases where they would matter.
654 *
655 * The cursor will be positioned at column 0 of the first inserted line.
656 */
657hterm.Terminal.prototype.appendRows_ = function(count) {
658 var cursorRow = this.screen_.rowsArray.length;
659 var offset = this.scrollbackRows_.length + cursorRow;
660 for (var i = 0; i < count; i++) {
661 var row = this.document_.createElement('x-row');
662 row.appendChild(this.document_.createTextNode(''));
663 row.rowIndex = offset + i;
664 this.screen_.pushRow(row);
665 }
666
667 var extraRows = this.screen_.rowsArray.length - this.screenSize.height;
668 if (extraRows > 0) {
669 var ary = this.screen_.shiftRows(extraRows);
670 Array.prototype.push.apply(this.scrollbackRows_, ary);
671 this.scheduleScrollDown_();
672 }
673
674 if (cursorRow >= this.screen_.rowsArray.length)
675 cursorRow = this.screen_.rowsArray.length - 1;
676
rginda87b86462011-12-14 13:48:03 -0800677 this.setAbsoluteCursorPosition(cursorRow, 0);
rginda8ba33642011-12-14 12:31:31 -0800678};
679
680/**
681 * Relocate rows from one part of the addressable screen to another.
682 *
683 * This is used to recycle rows during VT scrolls (those which are driven
684 * by VT commands, rather than by the user manipulating the scrollbar.)
685 *
686 * In this case, the blank lines scrolled into the scroll region are made of
687 * the nodes we scrolled off. These have their rowIndex properties carefully
688 * renumbered so as not to confuse the ScrollPort.
689 *
690 * TODO(rginda): I'm not sure why this doesn't require a scrollport repaint.
691 * It may just be luck. I wouldn't be surprised if we actually needed to call
692 * scrollPort_.invalidateRowRange, but I'm going to wait for evidence before
693 * adding it.
694 */
695hterm.Terminal.prototype.moveRows_ = function(fromIndex, count, toIndex) {
696 var ary = this.screen_.removeRows(fromIndex, count);
697 this.screen_.insertRows(toIndex, ary);
698
699 var start, end;
700 if (fromIndex < toIndex) {
701 start = fromIndex;
rginda87b86462011-12-14 13:48:03 -0800702 end = toIndex + count;
rginda8ba33642011-12-14 12:31:31 -0800703 } else {
704 start = toIndex;
rginda87b86462011-12-14 13:48:03 -0800705 end = fromIndex + count;
rginda8ba33642011-12-14 12:31:31 -0800706 }
707
708 this.renumberRows_(start, end);
rginda2312fff2012-01-05 16:20:52 -0800709 this.scrollPort_.scheduleInvalidate();
rginda8ba33642011-12-14 12:31:31 -0800710};
711
712/**
713 * Renumber the rowIndex property of the given range of rows.
714 *
715 * The start and end indicies are relative to the screen, not the scrollback.
716 * Rows in the scrollback buffer cannot be renumbered. Since they are not
rginda2312fff2012-01-05 16:20:52 -0800717 * addressable (you can't delete them, scroll them, etc), you should have
rginda8ba33642011-12-14 12:31:31 -0800718 * no need to renumber scrollback rows.
719 */
720hterm.Terminal.prototype.renumberRows_ = function(start, end) {
721 var offset = this.scrollbackRows_.length;
722 for (var i = start; i < end; i++) {
723 this.screen_.rowsArray[i].rowIndex = offset + i;
724 }
725};
726
727/**
728 * Print a string to the terminal.
729 *
730 * This respects the current insert and wraparound modes. It will add new lines
731 * to the end of the terminal, scrolling off the top into the scrollback buffer
732 * if necessary.
733 *
734 * The string is *not* parsed for escape codes. Use the interpret() method if
735 * that's what you're after.
736 *
737 * @param{string} str The string to print.
738 */
739hterm.Terminal.prototype.print = function(str) {
rgindaa19afe22012-01-25 15:40:22 -0800740 if (this.options_.wraparound && this.screen_.cursorPosition.overflow)
741 this.newLine();
rginda2312fff2012-01-05 16:20:52 -0800742
rgindaa19afe22012-01-25 15:40:22 -0800743 if (this.options_.insertMode) {
744 this.screen_.insertString(str);
745 } else {
746 this.screen_.overwriteString(str);
747 }
748
749 var overflow = this.screen_.maybeClipCurrentRow();
750
751 if (this.options_.wraparound && overflow) {
752 var lastColumn;
753
754 do {
755 if (this.screen_.cursorPosition.overflow)
756 this.newLine();
757
758 if (!this.options_.insertMode)
759 this.screen_.deleteChars(overflow.characterLength);
760
761 this.screen_.prependNodes(overflow);
762 lastColumn = overflow.characterCount;
763
764 overflow = this.screen_.maybeClipCurrentRow();
765 } while (overflow);
766
767 this.setCursorColumn(lastColumn);
768 }
rginda8ba33642011-12-14 12:31:31 -0800769
770 this.scheduleSyncCursorPosition_();
rginda0f5c0292012-01-13 11:00:13 -0800771
772 if (this.scrollOnOutput)
773 this.scrollPort_.scrollRowToBottom(this.getRowCount());
rginda8ba33642011-12-14 12:31:31 -0800774};
775
776/**
rginda87b86462011-12-14 13:48:03 -0800777 * Set the VT scroll region.
778 *
rginda87b86462011-12-14 13:48:03 -0800779 * This also resets the cursor position to the absolute (0, 0) position, since
780 * that's what xterm appears to do.
781 *
782 * @param {integer} scrollTop The zero-based top of the scroll region.
783 * @param {integer} scrollBottom The zero-based bottom of the scroll region,
784 * inclusive.
785 */
786hterm.Terminal.prototype.setVTScrollRegion = function(scrollTop, scrollBottom) {
787 this.vtScrollTop_ = scrollTop;
788 this.vtScrollBottom_ = scrollBottom;
789 this.setAbsoluteCursorPosition(0, 0);
790};
791
792/**
rginda8ba33642011-12-14 12:31:31 -0800793 * Return the top row index according to the VT.
794 *
795 * This will return 0 unless the terminal has been told to restrict scrolling
796 * to some lower row. It is used for some VT cursor positioning and scrolling
797 * commands.
798 *
799 * @return {integer} The topmost row in the terminal's scroll region.
800 */
801hterm.Terminal.prototype.getVTScrollTop = function() {
802 if (this.vtScrollTop_ != null)
803 return this.vtScrollTop_;
804
805 return 0;
rginda87b86462011-12-14 13:48:03 -0800806};
rginda8ba33642011-12-14 12:31:31 -0800807
808/**
809 * Return the bottom row index according to the VT.
810 *
811 * This will return the height of the terminal unless the it has been told to
812 * restrict scrolling to some higher row. It is used for some VT cursor
813 * positioning and scrolling commands.
814 *
815 * @return {integer} The bottommost row in the terminal's scroll region.
816 */
817hterm.Terminal.prototype.getVTScrollBottom = function() {
818 if (this.vtScrollBottom_ != null)
819 return this.vtScrollBottom_;
820
rginda87b86462011-12-14 13:48:03 -0800821 return this.screenSize.height - 1;
rginda8ba33642011-12-14 12:31:31 -0800822}
823
824/**
825 * Process a '\n' character.
826 *
827 * If the cursor is on the final row of the terminal this will append a new
828 * blank row to the screen and scroll the topmost row into the scrollback
829 * buffer.
830 *
831 * Otherwise, this moves the cursor to column zero of the next row.
832 */
833hterm.Terminal.prototype.newLine = function() {
834 if (this.screen_.cursorPosition.row == this.screen_.rowsArray.length - 1) {
rginda87b86462011-12-14 13:48:03 -0800835 // If we're at the end of the screen we need to append a new line and
836 // scroll the top line into the scrollback buffer.
rginda8ba33642011-12-14 12:31:31 -0800837 this.appendRows_(1);
rginda87b86462011-12-14 13:48:03 -0800838 } else if (this.screen_.cursorPosition.row == this.getVTScrollBottom()) {
839 // End of the scroll region does not affect the scrollback buffer.
840 this.vtScrollUp(1);
841 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, 0);
rginda8ba33642011-12-14 12:31:31 -0800842 } else {
rginda87b86462011-12-14 13:48:03 -0800843 // Anywhere else in the screen just moves the cursor.
844 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row + 1, 0);
rginda8ba33642011-12-14 12:31:31 -0800845 }
846};
847
848/**
849 * Like newLine(), except maintain the cursor column.
850 */
851hterm.Terminal.prototype.lineFeed = function() {
852 var column = this.screen_.cursorPosition.column;
853 this.newLine();
854 this.setCursorColumn(column);
855};
856
857/**
rginda87b86462011-12-14 13:48:03 -0800858 * If autoCarriageReturn is set then newLine(), else lineFeed().
859 */
860hterm.Terminal.prototype.formFeed = function() {
861 if (this.options_.autoCarriageReturn) {
862 this.newLine();
863 } else {
864 this.lineFeed();
865 }
866};
867
868/**
869 * Move the cursor up one row, possibly inserting a blank line.
870 *
871 * The cursor column is not changed.
872 */
873hterm.Terminal.prototype.reverseLineFeed = function() {
874 var scrollTop = this.getVTScrollTop();
875 var currentRow = this.screen_.cursorPosition.row;
876
877 if (currentRow == scrollTop) {
878 this.insertLines(1);
879 } else {
880 this.setAbsoluteCursorRow(currentRow - 1);
881 }
882};
883
884/**
rginda8ba33642011-12-14 12:31:31 -0800885 * Replace all characters to the left of the current cursor with the space
886 * character.
887 *
888 * TODO(rginda): This should probably *remove* the characters (not just replace
889 * with a space) if there are no characters at or beyond the current cursor
890 * position. Once it does that, it'll have the same text-attribute related
891 * issues as hterm.Screen.prototype.clearCursorRow :/
892 */
893hterm.Terminal.prototype.eraseToLeft = function() {
rginda87b86462011-12-14 13:48:03 -0800894 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -0800895 this.setCursorColumn(0);
rginda87b86462011-12-14 13:48:03 -0800896 this.screen_.overwriteString(hterm.getWhitespace(cursor.column + 1));
897 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800898};
899
900/**
901 * Erase a given number of characters to the right of the cursor, shifting
902 * remaining characters to the left.
903 *
904 * The cursor position is unchanged.
905 *
906 * TODO(rginda): Test that this works even when the cursor is positioned beyond
907 * the end of the text.
908 *
909 * TODO(rginda): This likely has text-attribute related troubles similar to the
910 * todo on hterm.Screen.prototype.clearCursorRow.
911 */
912hterm.Terminal.prototype.eraseToRight = function(opt_count) {
rginda87b86462011-12-14 13:48:03 -0800913 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -0800914
rginda87b86462011-12-14 13:48:03 -0800915 var maxCount = this.screenSize.width - cursor.column;
rginda8ba33642011-12-14 12:31:31 -0800916 var count = (opt_count && opt_count < maxCount) ? opt_count : maxCount;
917 this.screen_.deleteChars(count);
rginda87b86462011-12-14 13:48:03 -0800918 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800919};
920
921/**
922 * Erase the current line.
923 *
924 * The cursor position is unchanged.
925 *
926 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
927 * has a text-attribute related TODO.
928 */
929hterm.Terminal.prototype.eraseLine = function() {
rginda87b86462011-12-14 13:48:03 -0800930 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -0800931 this.screen_.clearCursorRow();
rginda87b86462011-12-14 13:48:03 -0800932 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800933};
934
935/**
936 * Erase all characters from the start of the scroll region to the current
937 * cursor position.
938 *
939 * The cursor position is unchanged.
940 *
941 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
942 * has a text-attribute related TODO.
943 */
944hterm.Terminal.prototype.eraseAbove = function() {
rginda87b86462011-12-14 13:48:03 -0800945 var cursor = this.saveCursor();
946
947 this.eraseToLeft();
rginda8ba33642011-12-14 12:31:31 -0800948
949 var top = this.getVTScrollTop();
rginda87b86462011-12-14 13:48:03 -0800950 for (var i = top; i < cursor.row; i++) {
951 this.setAbsoluteCursorPosition(i, 0);
rginda8ba33642011-12-14 12:31:31 -0800952 this.screen_.clearCursorRow();
953 }
954
rginda87b86462011-12-14 13:48:03 -0800955 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -0800956};
957
958/**
959 * Erase all characters from the current cursor position to the end of the
960 * scroll region.
961 *
962 * The cursor position is unchanged.
963 *
964 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
965 * has a text-attribute related TODO.
966 */
967hterm.Terminal.prototype.eraseBelow = function() {
rginda87b86462011-12-14 13:48:03 -0800968 var cursor = this.saveCursor();
969
970 this.eraseToRight();
rginda8ba33642011-12-14 12:31:31 -0800971
972 var bottom = this.getVTScrollBottom();
rginda87b86462011-12-14 13:48:03 -0800973 for (var i = cursor.row + 1; i <= bottom; i++) {
974 this.setAbsoluteCursorPosition(i, 0);
rginda8ba33642011-12-14 12:31:31 -0800975 this.screen_.clearCursorRow();
976 }
977
rginda87b86462011-12-14 13:48:03 -0800978 this.restoreCursor(cursor);
979};
980
981/**
982 * Fill the terminal with a given character.
983 *
984 * This methods does not respect the VT scroll region.
985 *
986 * @param {string} ch The character to use for the fill.
987 */
988hterm.Terminal.prototype.fill = function(ch) {
989 var cursor = this.saveCursor();
990
991 this.setAbsoluteCursorPosition(0, 0);
992 for (var row = 0; row < this.screenSize.height; row++) {
993 for (var col = 0; col < this.screenSize.width; col++) {
994 this.setAbsoluteCursorPosition(row, col);
995 this.screen_.overwriteString(ch);
996 }
997 }
998
999 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001000};
1001
1002/**
rgindae4d29232012-01-19 10:47:13 -08001003 * Erase the entire display.
rginda8ba33642011-12-14 12:31:31 -08001004 *
rgindae4d29232012-01-19 10:47:13 -08001005 * The cursor position is unchanged. This does not respect the scroll
1006 * region.
rginda8ba33642011-12-14 12:31:31 -08001007 *
1008 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1009 * has a text-attribute related TODO.
1010 */
1011hterm.Terminal.prototype.clear = function() {
rginda87b86462011-12-14 13:48:03 -08001012 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001013
rgindae4d29232012-01-19 10:47:13 -08001014 var bottom = this.screenSize.height;
rginda8ba33642011-12-14 12:31:31 -08001015
rgindae4d29232012-01-19 10:47:13 -08001016 for (var i = 0; i < bottom; i++) {
rginda87b86462011-12-14 13:48:03 -08001017 this.setAbsoluteCursorPosition(i, 0);
rginda8ba33642011-12-14 12:31:31 -08001018 this.screen_.clearCursorRow();
1019 }
1020
rginda87b86462011-12-14 13:48:03 -08001021 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001022};
1023
1024/**
1025 * VT command to insert lines at the current cursor row.
1026 *
1027 * This respects the current scroll region. Rows pushed off the bottom are
1028 * lost (they won't show up in the scrollback buffer).
1029 *
1030 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1031 * has a text-attribute related TODO.
1032 *
1033 * @param {integer} count The number of lines to insert.
1034 */
1035hterm.Terminal.prototype.insertLines = function(count) {
rginda87b86462011-12-14 13:48:03 -08001036 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001037
1038 var bottom = this.getVTScrollBottom();
rginda87b86462011-12-14 13:48:03 -08001039 count = Math.min(count, bottom - cursor.row);
rginda8ba33642011-12-14 12:31:31 -08001040
rgindae4d29232012-01-19 10:47:13 -08001041 var start = bottom - count + 1;
rginda87b86462011-12-14 13:48:03 -08001042 if (start != cursor.row)
1043 this.moveRows_(start, count, cursor.row);
rginda8ba33642011-12-14 12:31:31 -08001044
1045 for (var i = 0; i < count; i++) {
rginda87b86462011-12-14 13:48:03 -08001046 this.setAbsoluteCursorPosition(cursor.row + i, 0);
rginda8ba33642011-12-14 12:31:31 -08001047 this.screen_.clearCursorRow();
1048 }
1049
rginda87b86462011-12-14 13:48:03 -08001050 cursor.column = 0;
1051 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001052};
1053
1054/**
1055 * VT command to delete lines at the current cursor row.
1056 *
1057 * New rows are added to the bottom of scroll region to take their place. New
1058 * rows are strictly there to take up space and have no content or style.
1059 */
1060hterm.Terminal.prototype.deleteLines = function(count) {
rginda87b86462011-12-14 13:48:03 -08001061 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001062
rginda87b86462011-12-14 13:48:03 -08001063 var top = cursor.row;
rginda8ba33642011-12-14 12:31:31 -08001064 var bottom = this.getVTScrollBottom();
1065
rginda87b86462011-12-14 13:48:03 -08001066 var maxCount = bottom - top + 1;
rginda8ba33642011-12-14 12:31:31 -08001067 count = Math.min(count, maxCount);
1068
rginda87b86462011-12-14 13:48:03 -08001069 var moveStart = bottom - count + 1;
rginda8ba33642011-12-14 12:31:31 -08001070 if (count != maxCount)
1071 this.moveRows_(top, count, moveStart);
1072
1073 for (var i = 0; i < count; i++) {
rginda87b86462011-12-14 13:48:03 -08001074 this.setAbsoluteCursorPosition(moveStart + i, 0);
rginda8ba33642011-12-14 12:31:31 -08001075 this.screen_.clearCursorRow();
1076 }
1077
rginda87b86462011-12-14 13:48:03 -08001078 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001079};
1080
1081/**
1082 * Inserts the given number of spaces at the current cursor position.
1083 *
rginda87b86462011-12-14 13:48:03 -08001084 * The cursor position is not changed.
rginda8ba33642011-12-14 12:31:31 -08001085 */
1086hterm.Terminal.prototype.insertSpace = function(count) {
rginda87b86462011-12-14 13:48:03 -08001087 var cursor = this.saveCursor();
1088
rginda0f5c0292012-01-13 11:00:13 -08001089 var ws = hterm.getWhitespace(count || 1);
rginda8ba33642011-12-14 12:31:31 -08001090 this.screen_.insertString(ws);
rgindaa19afe22012-01-25 15:40:22 -08001091 this.screen_.maybeClipCurrentRow();
rginda87b86462011-12-14 13:48:03 -08001092
1093 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001094};
1095
1096/**
1097 * Forward-delete the specified number of characters starting at the cursor
1098 * position.
1099 *
1100 * @param {integer} count The number of characters to delete.
1101 */
1102hterm.Terminal.prototype.deleteChars = function(count) {
1103 this.screen_.deleteChars(count);
1104};
1105
1106/**
1107 * Shift rows in the scroll region upwards by a given number of lines.
1108 *
1109 * New rows are inserted at the bottom of the scroll region to fill the
1110 * vacated rows. The new rows not filled out with the current text attributes.
1111 *
1112 * This function does not affect the scrollback rows at all. Rows shifted
1113 * off the top are lost.
1114 *
rginda87b86462011-12-14 13:48:03 -08001115 * The cursor position is not altered.
1116 *
rginda8ba33642011-12-14 12:31:31 -08001117 * @param {integer} count The number of rows to scroll.
1118 */
1119hterm.Terminal.prototype.vtScrollUp = function(count) {
rginda87b86462011-12-14 13:48:03 -08001120 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001121
rginda87b86462011-12-14 13:48:03 -08001122 this.setAbsoluteCursorRow(this.getVTScrollTop());
rginda8ba33642011-12-14 12:31:31 -08001123 this.deleteLines(count);
1124
rginda87b86462011-12-14 13:48:03 -08001125 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001126};
1127
1128/**
1129 * Shift rows below the cursor down by a given number of lines.
1130 *
1131 * This function respects the current scroll region.
1132 *
1133 * New rows are inserted at the top of the scroll region to fill the
1134 * vacated rows. The new rows not filled out with the current text attributes.
1135 *
1136 * This function does not affect the scrollback rows at all. Rows shifted
1137 * off the bottom are lost.
1138 *
1139 * @param {integer} count The number of rows to scroll.
1140 */
1141hterm.Terminal.prototype.vtScrollDown = function(opt_count) {
rginda87b86462011-12-14 13:48:03 -08001142 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001143
rginda87b86462011-12-14 13:48:03 -08001144 this.setAbsoluteCursorPosition(this.getVTScrollTop(), 0);
rginda8ba33642011-12-14 12:31:31 -08001145 this.insertLines(opt_count);
1146
rginda87b86462011-12-14 13:48:03 -08001147 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001148};
1149
rginda87b86462011-12-14 13:48:03 -08001150
rginda8ba33642011-12-14 12:31:31 -08001151/**
1152 * Set the cursor position.
1153 *
1154 * The cursor row is relative to the scroll region if the terminal has
1155 * 'origin mode' enabled, or relative to the addressable screen otherwise.
1156 *
1157 * @param {integer} row The new zero-based cursor row.
1158 * @param {integer} row The new zero-based cursor column.
1159 */
1160hterm.Terminal.prototype.setCursorPosition = function(row, column) {
1161 if (this.options_.originMode) {
rginda87b86462011-12-14 13:48:03 -08001162 this.setRelativeCursorPosition(row, column);
rginda8ba33642011-12-14 12:31:31 -08001163 } else {
rginda87b86462011-12-14 13:48:03 -08001164 this.setAbsoluteCursorPosition(row, column);
rginda8ba33642011-12-14 12:31:31 -08001165 }
rginda87b86462011-12-14 13:48:03 -08001166};
rginda8ba33642011-12-14 12:31:31 -08001167
rginda87b86462011-12-14 13:48:03 -08001168hterm.Terminal.prototype.setRelativeCursorPosition = function(row, column) {
1169 var scrollTop = this.getVTScrollTop();
1170 row = hterm.clamp(row + scrollTop, scrollTop, this.getVTScrollBottom());
rginda2312fff2012-01-05 16:20:52 -08001171 column = hterm.clamp(column, 0, this.screenSize.width - 1);
rginda87b86462011-12-14 13:48:03 -08001172 this.screen_.setCursorPosition(row, column);
1173};
1174
1175hterm.Terminal.prototype.setAbsoluteCursorPosition = function(row, column) {
rginda2312fff2012-01-05 16:20:52 -08001176 row = hterm.clamp(row, 0, this.screenSize.height - 1);
1177 column = hterm.clamp(column, 0, this.screenSize.width - 1);
rginda8ba33642011-12-14 12:31:31 -08001178 this.screen_.setCursorPosition(row, column);
1179};
1180
1181/**
1182 * Set the cursor column.
1183 *
1184 * @param {integer} column The new zero-based cursor column.
1185 */
1186hterm.Terminal.prototype.setCursorColumn = function(column) {
rginda87b86462011-12-14 13:48:03 -08001187 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, column);
rginda8ba33642011-12-14 12:31:31 -08001188};
1189
1190/**
1191 * Return the cursor column.
1192 *
1193 * @return {integer} The zero-based cursor column.
1194 */
1195hterm.Terminal.prototype.getCursorColumn = function() {
1196 return this.screen_.cursorPosition.column;
1197};
1198
1199/**
1200 * Set the cursor row.
1201 *
1202 * The cursor row is relative to the scroll region if the terminal has
1203 * 'origin mode' enabled, or relative to the addressable screen otherwise.
1204 *
1205 * @param {integer} row The new cursor row.
1206 */
rginda87b86462011-12-14 13:48:03 -08001207hterm.Terminal.prototype.setAbsoluteCursorRow = function(row) {
1208 this.setAbsoluteCursorPosition(row, this.screen_.cursorPosition.column);
rginda8ba33642011-12-14 12:31:31 -08001209};
1210
1211/**
1212 * Return the cursor row.
1213 *
1214 * @return {integer} The zero-based cursor row.
1215 */
1216hterm.Terminal.prototype.getCursorRow = function(row) {
1217 return this.screen_.cursorPosition.row;
1218};
1219
1220/**
1221 * Request that the ScrollPort redraw itself soon.
1222 *
1223 * The redraw will happen asynchronously, soon after the call stack winds down.
1224 * Multiple calls will be coalesced into a single redraw.
1225 */
1226hterm.Terminal.prototype.scheduleRedraw_ = function() {
rginda87b86462011-12-14 13:48:03 -08001227 if (this.timeouts_.redraw)
1228 return;
rginda8ba33642011-12-14 12:31:31 -08001229
1230 var self = this;
rginda87b86462011-12-14 13:48:03 -08001231 this.timeouts_.redraw = setTimeout(function() {
1232 delete self.timeouts_.redraw;
rginda8ba33642011-12-14 12:31:31 -08001233 self.scrollPort_.redraw_();
1234 }, 0);
1235};
1236
1237/**
1238 * Request that the ScrollPort be scrolled to the bottom.
1239 *
1240 * The scroll will happen asynchronously, soon after the call stack winds down.
1241 * Multiple calls will be coalesced into a single scroll.
1242 *
1243 * This affects the scrollbar position of the ScrollPort, and has nothing to
1244 * do with the VT scroll commands.
1245 */
1246hterm.Terminal.prototype.scheduleScrollDown_ = function() {
1247 if (this.timeouts_.scrollDown)
rginda87b86462011-12-14 13:48:03 -08001248 return;
rginda8ba33642011-12-14 12:31:31 -08001249
1250 var self = this;
1251 this.timeouts_.scrollDown = setTimeout(function() {
1252 delete self.timeouts_.scrollDown;
1253 self.scrollPort_.scrollRowToBottom(self.getRowCount());
1254 }, 10);
1255};
1256
1257/**
1258 * Move the cursor up a specified number of rows.
1259 *
1260 * @param {integer} count The number of rows to move the cursor.
1261 */
1262hterm.Terminal.prototype.cursorUp = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001263 return this.cursorDown(-(count || 1));
rginda8ba33642011-12-14 12:31:31 -08001264};
1265
1266/**
1267 * Move the cursor down a specified number of rows.
1268 *
1269 * @param {integer} count The number of rows to move the cursor.
1270 */
1271hterm.Terminal.prototype.cursorDown = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001272 count = count || 1;
rginda8ba33642011-12-14 12:31:31 -08001273 var minHeight = (this.options_.originMode ? this.getVTScrollTop() : 0);
1274 var maxHeight = (this.options_.originMode ? this.getVTScrollBottom() :
1275 this.screenSize.height - 1);
1276
1277 var row = hterm.clamp(this.screen_.cursorPosition.row + count,
1278 minHeight, maxHeight);
rginda87b86462011-12-14 13:48:03 -08001279 this.setAbsoluteCursorRow(row);
rginda8ba33642011-12-14 12:31:31 -08001280};
1281
1282/**
1283 * Move the cursor left a specified number of columns.
1284 *
1285 * @param {integer} count The number of columns to move the cursor.
1286 */
1287hterm.Terminal.prototype.cursorLeft = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001288 return this.cursorRight(-(count || 1));
rginda8ba33642011-12-14 12:31:31 -08001289};
1290
1291/**
1292 * Move the cursor right a specified number of columns.
1293 *
1294 * @param {integer} count The number of columns to move the cursor.
1295 */
1296hterm.Terminal.prototype.cursorRight = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001297 count = count || 1;
rginda8ba33642011-12-14 12:31:31 -08001298 var column = hterm.clamp(this.screen_.cursorPosition.column + count,
rginda87b86462011-12-14 13:48:03 -08001299 0, this.screenSize.width - 1);
rginda8ba33642011-12-14 12:31:31 -08001300 this.setCursorColumn(column);
1301};
1302
1303/**
1304 * Reverse the foreground and background colors of the terminal.
1305 *
1306 * This only affects text that was drawn with no attributes.
1307 *
1308 * TODO(rginda): Test xterm to see if reverse is respected for text that has
1309 * been drawn with attributes that happen to coincide with the default
1310 * 'no-attribute' colors. My guess is probably not.
1311 */
1312hterm.Terminal.prototype.setReverseVideo = function(state) {
rginda87b86462011-12-14 13:48:03 -08001313 this.options_.reverseVideo = state;
rginda8ba33642011-12-14 12:31:31 -08001314 if (state) {
1315 this.scrollPort_.setForegroundColor(this.backgroundColor);
1316 this.scrollPort_.setBackgroundColor(this.foregroundColor);
1317 } else {
1318 this.scrollPort_.setForegroundColor(this.foregroundColor);
1319 this.scrollPort_.setBackgroundColor(this.backgroundColor);
1320 }
1321};
1322
1323/**
rginda87b86462011-12-14 13:48:03 -08001324 * Ring the terminal bell.
1325 *
1326 * We only have a visual bell, which quickly toggles inverse video in the
1327 * terminal.
1328 */
1329hterm.Terminal.prototype.ringBell = function() {
rginda6d397402012-01-17 10:58:29 -08001330 this.cursorNode_.style.backgroundColor =
1331 this.scrollPort_.getForegroundColor();
rginda87b86462011-12-14 13:48:03 -08001332
1333 var self = this;
1334 setTimeout(function() {
rginda6d397402012-01-17 10:58:29 -08001335 self.cursorNode_.style.backgroundColor = self.cursorColor;
1336 }, 200);
rginda87b86462011-12-14 13:48:03 -08001337};
1338
1339/**
rginda8ba33642011-12-14 12:31:31 -08001340 * Set the origin mode bit.
1341 *
1342 * If origin mode is on, certain VT cursor and scrolling commands measure their
1343 * row parameter relative to the VT scroll region. Otherwise, row 0 corresponds
1344 * to the top of the addressable screen.
1345 *
1346 * Defaults to off.
1347 *
1348 * @param {boolean} state True to set origin mode, false to unset.
1349 */
1350hterm.Terminal.prototype.setOriginMode = function(state) {
1351 this.options_.originMode = state;
rgindae4d29232012-01-19 10:47:13 -08001352 this.setCursorPosition(0, 0);
rginda8ba33642011-12-14 12:31:31 -08001353};
1354
1355/**
1356 * Set the insert mode bit.
1357 *
1358 * If insert mode is on, existing text beyond the cursor position will be
1359 * shifted right to make room for new text. Otherwise, new text overwrites
1360 * any existing text.
1361 *
1362 * Defaults to off.
1363 *
1364 * @param {boolean} state True to set insert mode, false to unset.
1365 */
1366hterm.Terminal.prototype.setInsertMode = function(state) {
1367 this.options_.insertMode = state;
1368};
1369
1370/**
rginda87b86462011-12-14 13:48:03 -08001371 * Set the auto carriage return bit.
1372 *
1373 * If auto carriage return is on then a formfeed character is interpreted
1374 * as a newline, otherwise it's the same as a linefeed. The difference boils
1375 * down to whether or not the cursor column is reset.
1376 */
1377hterm.Terminal.prototype.setAutoCarriageReturn = function(state) {
1378 this.options_.autoCarriageReturn = state;
1379};
1380
1381/**
rginda8ba33642011-12-14 12:31:31 -08001382 * Set the wraparound mode bit.
1383 *
1384 * If wraparound mode is on, certain VT commands will allow the cursor to wrap
1385 * to the start of the following row. Otherwise, the cursor is clamped to the
1386 * end of the screen and attempts to write past it are ignored.
1387 *
1388 * Defaults to on.
1389 *
1390 * @param {boolean} state True to set wraparound mode, false to unset.
1391 */
1392hterm.Terminal.prototype.setWraparound = function(state) {
1393 this.options_.wraparound = state;
1394};
1395
1396/**
1397 * Set the reverse-wraparound mode bit.
1398 *
1399 * If wraparound mode is off, certain VT commands will allow the cursor to wrap
1400 * to the end of the previous row. Otherwise, the cursor is clamped to column
1401 * 0.
1402 *
1403 * Defaults to off.
1404 *
1405 * @param {boolean} state True to set reverse-wraparound mode, false to unset.
1406 */
1407hterm.Terminal.prototype.setReverseWraparound = function(state) {
1408 this.options_.reverseWraparound = state;
1409};
1410
1411/**
1412 * Selects between the primary and alternate screens.
1413 *
1414 * If alternate mode is on, the alternate screen is active. Otherwise the
1415 * primary screen is active.
1416 *
1417 * Swapping screens has no effect on the scrollback buffer.
1418 *
1419 * Each screen maintains its own cursor position.
1420 *
1421 * Defaults to off.
1422 *
1423 * @param {boolean} state True to set alternate mode, false to unset.
1424 */
1425hterm.Terminal.prototype.setAlternateMode = function(state) {
rginda6d397402012-01-17 10:58:29 -08001426 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001427 this.screen_ = state ? this.alternateScreen_ : this.primaryScreen_;
1428
1429 this.screen_.setColumnCount(this.screenSize.width);
1430
1431 var rowDelta = this.screenSize.height - this.screen_.getHeight();
1432 if (rowDelta > 0)
1433 this.appendRows_(rowDelta);
1434
rginda6d397402012-01-17 10:58:29 -08001435 this.restoreCursor(cursor);
1436
rginda2312fff2012-01-05 16:20:52 -08001437 this.scrollPort_.invalidate();
rginda8ba33642011-12-14 12:31:31 -08001438 this.syncCursorPosition_();
1439};
1440
1441/**
1442 * Set the cursor-blink mode bit.
1443 *
1444 * If cursor-blink is on, the cursor will blink when it is visible. Otherwise
1445 * a visible cursor does not blink.
1446 *
1447 * You should make sure to turn blinking off if you're going to dispose of a
1448 * terminal, otherwise you'll leak a timeout.
1449 *
1450 * Defaults to on.
1451 *
1452 * @param {boolean} state True to set cursor-blink mode, false to unset.
1453 */
1454hterm.Terminal.prototype.setCursorBlink = function(state) {
1455 this.options_.cursorBlink = state;
1456
1457 if (!state && this.timeouts_.cursorBlink) {
1458 clearTimeout(this.timeouts_.cursorBlink);
1459 delete this.timeouts_.cursorBlink;
1460 }
1461
1462 if (this.options_.cursorVisible)
1463 this.setCursorVisible(true);
1464};
1465
1466/**
1467 * Set the cursor-visible mode bit.
1468 *
1469 * If cursor-visible is on, the cursor will be visible. Otherwise it will not.
1470 *
1471 * Defaults to on.
1472 *
1473 * @param {boolean} state True to set cursor-visible mode, false to unset.
1474 */
1475hterm.Terminal.prototype.setCursorVisible = function(state) {
1476 this.options_.cursorVisible = state;
1477
1478 if (!state) {
rginda87b86462011-12-14 13:48:03 -08001479 this.cursorNode_.style.opacity = '0';
rginda8ba33642011-12-14 12:31:31 -08001480 return;
1481 }
1482
rginda87b86462011-12-14 13:48:03 -08001483 this.syncCursorPosition_();
1484
1485 this.cursorNode_.style.opacity = '1';
rginda8ba33642011-12-14 12:31:31 -08001486
1487 if (this.options_.cursorBlink) {
1488 if (this.timeouts_.cursorBlink)
1489 return;
1490
1491 this.timeouts_.cursorBlink = setInterval(this.onCursorBlink_.bind(this),
1492 500);
1493 } else {
1494 if (this.timeouts_.cursorBlink) {
1495 clearTimeout(this.timeouts_.cursorBlink);
1496 delete this.timeouts_.cursorBlink;
1497 }
1498 }
1499};
1500
1501/**
rginda87b86462011-12-14 13:48:03 -08001502 * Synchronizes the visible cursor and document selection with the current
1503 * cursor coordinates.
rginda8ba33642011-12-14 12:31:31 -08001504 */
1505hterm.Terminal.prototype.syncCursorPosition_ = function() {
1506 var topRowIndex = this.scrollPort_.getTopRowIndex();
1507 var bottomRowIndex = this.scrollPort_.getBottomRowIndex(topRowIndex);
1508 var cursorRowIndex = this.scrollbackRows_.length +
1509 this.screen_.cursorPosition.row;
1510
1511 if (cursorRowIndex > bottomRowIndex) {
1512 // Cursor is scrolled off screen, move it outside of the visible area.
1513 this.cursorNode_.style.top = -this.characterSize_.height;
1514 return;
1515 }
1516
1517 this.cursorNode_.style.top = this.scrollPort_.visibleRowTopMargin +
1518 this.characterSize_.height * (cursorRowIndex - topRowIndex);
1519 this.cursorNode_.style.left = this.characterSize_.width *
1520 this.screen_.cursorPosition.column;
rginda87b86462011-12-14 13:48:03 -08001521
1522 this.cursorNode_.setAttribute('title',
1523 '(' + this.screen_.cursorPosition.row +
1524 ', ' + this.screen_.cursorPosition.column +
1525 ')');
1526
1527 // Update the caret for a11y purposes.
1528 var selection = this.document_.getSelection();
1529 if (selection && selection.isCollapsed)
1530 this.screen_.syncSelectionCaret(selection);
rginda8ba33642011-12-14 12:31:31 -08001531};
1532
1533/**
1534 * Synchronizes the visible cursor with the current cursor coordinates.
1535 *
1536 * The sync will happen asynchronously, soon after the call stack winds down.
1537 * Multiple calls will be coalesced into a single sync.
1538 */
1539hterm.Terminal.prototype.scheduleSyncCursorPosition_ = function() {
1540 if (this.timeouts_.syncCursor)
rginda87b86462011-12-14 13:48:03 -08001541 return;
rginda8ba33642011-12-14 12:31:31 -08001542
1543 var self = this;
1544 this.timeouts_.syncCursor = setTimeout(function() {
1545 self.syncCursorPosition_();
1546 delete self.timeouts_.syncCursor;
rginda87b86462011-12-14 13:48:03 -08001547 }, 0);
1548};
1549
1550/**
1551 * Invoked by hterm.Terminal.Keyboard when a VT keystroke is detected.
1552 *
1553 * @param {string} string The VT string representing the keystroke.
1554 */
1555hterm.Terminal.prototype.onVTKeystroke = function(string) {
1556 if (this.scrollOnKeystroke)
1557 this.scrollPort_.scrollRowToBottom(this.getRowCount());
1558
1559 this.io.onVTKeystroke(string);
rginda8ba33642011-12-14 12:31:31 -08001560};
1561
1562/**
1563 * React when the ScrollPort is scrolled.
1564 */
1565hterm.Terminal.prototype.onScroll_ = function() {
1566 this.scheduleSyncCursorPosition_();
1567};
1568
1569/**
rginda9846e2f2012-01-27 13:53:33 -08001570 * React when text is pasted into the scrollPort.
1571 */
1572hterm.Terminal.prototype.onPaste_ = function(e) {
1573 this.io.onVTKeystroke(e.text);
1574};
1575
1576/**
rginda8ba33642011-12-14 12:31:31 -08001577 * React when the ScrollPort is resized.
rgindac9bc5502012-01-18 11:48:44 -08001578 *
1579 * Note: This function should not directly contain code that alters the internal
1580 * state of the terminal. That kind of code belongs in realizeWidth or
1581 * realizeHeight, so that it can be executed synchronously in the case of a
1582 * programmatic width change.
rginda8ba33642011-12-14 12:31:31 -08001583 */
1584hterm.Terminal.prototype.onResize_ = function() {
rgindac9bc5502012-01-18 11:48:44 -08001585 var columnCount = Math.floor(this.scrollPort_.getScreenWidth() /
1586 this.characterSize_.width);
rgindac9bc5502012-01-18 11:48:44 -08001587 var rowCount = this.scrollPort_.visibleRowCount;
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +04001588 this.realizeSize_(columnCount, rowCount);
rgindac9bc5502012-01-18 11:48:44 -08001589 this.scheduleSyncCursorPosition_();
rginda8ba33642011-12-14 12:31:31 -08001590};
1591
1592/**
1593 * Service the cursor blink timeout.
1594 */
1595hterm.Terminal.prototype.onCursorBlink_ = function() {
rginda87b86462011-12-14 13:48:03 -08001596 if (this.cursorNode_.style.opacity == '0') {
1597 this.cursorNode_.style.opacity = '1';
rginda8ba33642011-12-14 12:31:31 -08001598 } else {
rginda87b86462011-12-14 13:48:03 -08001599 this.cursorNode_.style.opacity = '0';
rginda8ba33642011-12-14 12:31:31 -08001600 }
1601};