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