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