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