Chromium Code Reviews| 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 * @fileoverview This class represents a single terminal screen full of text. | |
| 7 * | |
| 8 * It maintains the current cursor position and has basic methods for text | |
| 9 * insert and overwrite, and adding or removing rows from the screen. | |
| 10 * | |
| 11 * This class has no knowledge of the scrollback buffer. | |
| 12 * | |
| 13 * The number of rows on the screen is determined only by the number of rows | |
| 14 * that the caller inserts into the screen. If a caller wants to ensure a | |
| 15 * constant number of rows on the screen, it's their responsibility to remove a | |
| 16 * row for each row inserted. | |
| 17 * | |
| 18 * The screen width, in contrast, is enforced locally. | |
| 19 * | |
| 20 * | |
| 21 * In practice... | |
| 22 * - The hterm.Terminal class holds two hterm.Screen instances. One for the | |
| 23 * primary screen and one for the alternate screen. | |
| 24 * | |
| 25 * - The html.Screen class only cares that rows are HTMLElements. In the | |
| 26 * larger context of hterm, however, the rows happen to be displayed by an | |
| 27 * hterm.ScrollPort and have to follow a few rules as a result. Each | |
| 28 * row must be rooted by the custom HTML tag 'x-row', and each must have a | |
| 29 * rowIndex property that corresponds to the index of the row in the context | |
| 30 * of the scrollback buffer. These invariants are enforced by hterm.Terminal | |
| 31 * because that is the class using the hterm.Screen in the context of an | |
| 32 * hterm.ScrollPort. | |
| 33 */ | |
| 34 | |
| 35 /** | |
| 36 * Create a new screen instance. | |
| 37 * | |
| 38 * The screen initially has no rows and a maximum column count of 0. | |
| 39 * | |
| 40 * @param {integer} opt_columnCount The maximum number of columns for this | |
| 41 * screen. See insertString() and overwriteString() for information about | |
| 42 * what happens when too many characters are added too a row. Defaults to | |
| 43 * 0 if not provided. | |
| 44 */ | |
| 45 hterm.Screen = function(opt_columnCount) { | |
| 46 /** | |
| 47 * Public, read-only access to the rows in this screen. | |
| 48 */ | |
| 49 this.rowsArray = []; | |
| 50 | |
| 51 // The max column width for this screen. | |
| 52 this.columnCount_ = opt_columnCount || 0; | |
| 53 | |
| 54 // Current zero-based cursor coordinates. (-1, -1) implies that the cursor | |
| 55 // is uninitialized. | |
| 56 this.cursorPosition = new hterm.RowCol(-1, -1); | |
| 57 | |
| 58 // The node containing the row that the cursor is positioned on. | |
| 59 this.cursorRowNode_ = null; | |
| 60 | |
| 61 // The node containing the span of text that the cursor is positioned on. | |
| 62 this.cursorNode_ = null; | |
| 63 | |
| 64 // The offset into cursorNode_ where the cursor is positioned. | |
| 65 this.cursorOffset_ = null; | |
| 66 }; | |
| 67 | |
| 68 /** | |
| 69 * Return the screen size as an hterm.Size object. | |
| 70 * | |
| 71 * @return {hterm.Size} hterm.Size object representing the current number | |
| 72 * of rows and columns in this screen. | |
| 73 */ | |
| 74 hterm.Screen.prototype.getSize = function() { | |
| 75 return new hterm.Size(this.columnCount_, this.rowsArray.length); | |
| 76 }; | |
| 77 | |
| 78 /** | |
| 79 * Return the current number of rows in this screen. | |
| 80 * | |
| 81 * @return {integer} The number of rows in this screen. | |
| 82 */ | |
| 83 hterm.Screen.prototype.getHeight = function() { | |
| 84 return this.rowsArray.length; | |
| 85 }; | |
| 86 | |
| 87 /** | |
| 88 * Return the current number of columns in this screen. | |
| 89 * | |
| 90 * @return {integer} The number of columns in this screen. | |
| 91 */ | |
| 92 hterm.Screen.prototype.getWidth = function() { | |
| 93 return this.columnCount_; | |
| 94 }; | |
| 95 | |
| 96 /** | |
| 97 * Set the maximum number of columns per row. | |
| 98 * | |
| 99 * TODO(rginda): This should probably clip existing rows if the count is | |
| 100 * decreased. | |
| 101 * | |
| 102 * @param {integer} count The maximum number of columns per row. | |
| 103 */ | |
| 104 hterm.Screen.prototype.setColumnCount = function(count) { | |
| 105 this.columnCount_ = count; | |
| 106 }; | |
| 107 | |
| 108 /** | |
| 109 * Remove the first row from the screen and return it. | |
| 110 * | |
| 111 * @return {HTMLElement} The first row in this screen. | |
| 112 */ | |
| 113 hterm.Screen.prototype.shiftRow = function() { | |
| 114 return this.shiftRows(1)[0]; | |
| 115 } | |
| 116 | |
| 117 /** | |
| 118 * Remove rows from the top of the screen and return them as an array. | |
| 119 * | |
| 120 * @param {integer} count The number of rows to remove. | |
| 121 * @return {Array.<HTMLElement>} The selected rows. | |
| 122 */ | |
| 123 hterm.Screen.prototype.shiftRows = function(count) { | |
| 124 return this.rowsArray.splice(0, count); | |
| 125 }; | |
| 126 | |
| 127 /** | |
| 128 * Insert a row at the top of the screen. | |
| 129 * | |
| 130 * @param {HTMLElement} The row to insert. | |
| 131 */ | |
| 132 hterm.Screen.prototype.unshiftRow = function(row) { | |
| 133 this.rowsArray.splice(0, 0, row); | |
| 134 }; | |
| 135 | |
| 136 /** | |
| 137 * Insert rows at the top of the screen. | |
| 138 * | |
| 139 * @param {Array.<HTMLElement>} The rows to insert. | |
| 140 */ | |
| 141 hterm.Screen.prototype.unshiftRows = function(rows) { | |
| 142 rows.unshift.apply(this.rowsArray, rows); | |
|
dgozman
2011/11/29 11:35:42
Why rows.unshift.apply, and not this.rowsArray.uns
rginda
2011/11/29 19:26:12
Done.
| |
| 143 }; | |
| 144 | |
| 145 /** | |
| 146 * Remove the last row from the screen and return it. | |
| 147 * | |
| 148 * @return {HTMLElement} The last row in this screen. | |
| 149 */ | |
| 150 hterm.Screen.prototype.popRow = function() { | |
| 151 return this.popRows(1)[0]; | |
| 152 }; | |
| 153 | |
| 154 /** | |
| 155 * Remove rows from the bottom of the screen and return them as an array. | |
| 156 * | |
| 157 * @param {integer} count The number of rows to remove. | |
| 158 * @return {Array.<HTMLElement>} The selected rows. | |
| 159 */ | |
| 160 hterm.Screen.prototype.popRows = function(count) { | |
| 161 return this.rowsArray.splice(this.rowsArray.length - count, count); | |
| 162 }; | |
| 163 | |
| 164 /** | |
| 165 * Insert a row at the bottom of the screen. | |
| 166 * | |
| 167 * @param {HTMLElement} The row to insert. | |
| 168 */ | |
| 169 hterm.Screen.prototype.pushRow = function(row) { | |
| 170 var start = this.rowsArray.length; | |
|
dgozman
2011/11/29 11:35:42
start not used
rginda
2011/11/29 19:26:12
Done.
| |
| 171 this.rowsArray.push(row); | |
| 172 }; | |
| 173 | |
| 174 /** | |
| 175 * Insert rows at the bottom of the screen. | |
| 176 * | |
| 177 * @param {Array.<HTMLElement>} The rows to insert. | |
| 178 */ | |
| 179 hterm.Screen.prototype.pushRows = function(rows) { | |
| 180 var start = this.rowsArray.length; | |
|
dgozman
2011/11/29 11:35:42
start not used
rginda
2011/11/29 19:26:12
Done.
| |
| 181 rows.push.apply(this.rowsArray, rows); | |
| 182 }; | |
| 183 | |
| 184 /** | |
| 185 * Insert a row at the specified column of the screen. | |
| 186 * | |
| 187 * @param {HTMLElement} The row to insert. | |
| 188 */ | |
| 189 hterm.Screen.prototype.insertRow = function(index, row) { | |
| 190 this.rowsArray.splice(index, 0, row); | |
| 191 }; | |
| 192 | |
| 193 /** | |
| 194 * Insert rows at the specified column of the screen. | |
| 195 * | |
| 196 * @param {Array.<HTMLElement>} The rows to insert. | |
| 197 */ | |
| 198 hterm.Screen.prototype.insertRows = function(index, rows) { | |
| 199 for (var i = 0; i < rows.length; i++) { | |
| 200 this.rowsArray.splice(index + i, 0, rows[i]); | |
|
arv (Not doing code reviews)
2011/11/28 18:21:00
Array.prototype.splice.apply(this.rowsArray, index
rginda
2011/11/28 20:39:47
That's the way I had written it at first, but it's
arv (Not doing code reviews)
2011/11/28 21:33:22
My bad.
You could use bind but at this point I'm
dgozman
2011/11/29 11:35:42
Maybe, splice.apply will be faster? It's just two
rginda
2011/11/29 19:26:12
If I modify the rows array it'll trash something t
| |
| 201 } | |
| 202 }; | |
| 203 | |
| 204 /** | |
| 205 * Remove a last row from the specified column of the screen and return it. | |
| 206 * | |
| 207 * @return {HTMLElement} The selected row. | |
| 208 */ | |
| 209 hterm.Screen.prototype.removeRow = function(index) { | |
| 210 return this.rowsArray.splice(index, 1)[0]; | |
| 211 }; | |
| 212 | |
| 213 /** | |
| 214 * Remove rows from the bottom of the screen and return them as an array. | |
| 215 * | |
| 216 * @param {integer} count The number of rows to remove. | |
| 217 * @return {Array.<HTMLElement>} The selected rows. | |
| 218 */ | |
| 219 hterm.Screen.prototype.removeRows = function(index, count) { | |
| 220 return this.rowsArray.splice(index, count); | |
| 221 }; | |
| 222 | |
| 223 /** | |
| 224 * Invalidate the current cursor position. | |
| 225 * | |
| 226 * This sets this.cursorPosition to (-1, -1) and clears out some internal | |
| 227 * data. | |
| 228 * | |
| 229 * Attempting to insert or overwrite text while the cursor position is invalid | |
| 230 * will raise an obscure exception. | |
| 231 */ | |
| 232 hterm.Screen.prototype.invalidateCursorPosition = function() { | |
| 233 this.cursorPosition.move(-1, -1); | |
| 234 this.cursorRowNode_ = null; | |
| 235 this.cursorNode_ = null; | |
| 236 this.cursorOffset_ = null; | |
| 237 }; | |
| 238 | |
| 239 /** | |
| 240 * Clear the contents of a selected row. | |
| 241 * | |
| 242 * TODO: Make this clear in the current style... somehow. We can't just | |
| 243 * fill the row with spaces, since they would have potential to mess up the | |
| 244 * terminal (for example, in insert mode, they might wrap around to the next | |
| 245 * line. | |
| 246 * | |
| 247 * @param {integer} index The zero-based index to clear. | |
| 248 */ | |
| 249 hterm.Screen.prototype.clearRow = function(index) { | |
| 250 if (index == this.cursorPosition.row) { | |
| 251 this.clearCursorRow(); | |
| 252 } else { | |
| 253 var row = this.rowsArray[index]; | |
| 254 row.innerHTML = ''; | |
| 255 row.appendChild(row.ownerDocument.createTextNode('')) | |
|
dgozman
2011/11/29 11:35:42
semicolon
rginda
2011/11/29 19:26:12
Done.
| |
| 256 } | |
| 257 }; | |
| 258 | |
| 259 /** | |
| 260 * Clear the contents of the cursor row. | |
| 261 * | |
| 262 * TODO: Same comment as clearRow(). | |
| 263 */ | |
| 264 hterm.Screen.prototype.clearCursorRow = function() { | |
| 265 this.cursorRowNode_.innerHTML = ''; | |
| 266 var text = this.cursorRowNode_.ownerDocument.createTextNode(''); | |
| 267 this.cursorRowNode_.appendChild(text); | |
| 268 this.cursorOffset_ = 0; | |
| 269 this.cursorNode_ = text; | |
| 270 this.cursorPosition.column = 0; | |
| 271 }; | |
| 272 | |
| 273 /** | |
| 274 * Relocate the cursor to a give row and column. | |
| 275 * | |
| 276 * @param {integer} row The zero based row. | |
| 277 * @param {integer} column The zero based column. | |
| 278 */ | |
| 279 hterm.Screen.prototype.setCursorPosition = function(row, column) { | |
| 280 var currentColumn = 0; | |
| 281 if (row >= this.rowsArray.length) | |
| 282 throw 'Row out of bounds: ' + row; | |
| 283 | |
| 284 var rowNode = this.rowsArray[row]; | |
| 285 var node = rowNode.firstChild; | |
| 286 | |
| 287 if (!node) { | |
| 288 node = rowNode.ownerDocument.createTextNode(''); | |
| 289 rowNode.appendChild(node); | |
| 290 } | |
| 291 | |
| 292 if (rowNode == this.cursorRowNode_) { | |
| 293 if (column >= this.cursorPosition.column - this.cursorOffset_) { | |
| 294 node = this.cursorNode_; | |
| 295 currentColumn = this.cursorPosition.column - this.cursorOffset_; | |
| 296 } | |
| 297 } else { | |
| 298 this.cursorRowNode_ = rowNode; | |
| 299 } | |
| 300 | |
| 301 this.cursorPosition.move(row, column); | |
| 302 | |
| 303 while (node) { | |
| 304 var offset = column - currentColumn; | |
| 305 var textContent = node.textContent; | |
| 306 if (!node.nextSibling || textContent.length > offset) { | |
| 307 this.cursorNode_ = node; | |
| 308 this.cursorOffset_ = offset; | |
| 309 return; | |
| 310 } | |
| 311 | |
| 312 currentColumn += textContent.length; | |
| 313 node = node.nextSibling; | |
|
dgozman
2011/11/29 11:35:42
This code assumes that each row may contain only d
rginda
2011/11/29 19:26:12
We're going to want to keep this requirement to si
| |
| 314 } | |
| 315 }; | |
| 316 | |
| 317 /** | |
| 318 * Insert the given string at the cursor position, with the understanding that | |
| 319 * the insert will cause the column to overflow, and the overflow will be | |
| 320 * in a different text style than where the cursor is currently located. | |
| 321 * | |
| 322 * TODO: Implement this. | |
| 323 */ | |
| 324 hterm.Screen.prototype.spliceStringAndWrap_ = function(str) { | |
| 325 throw 'NOT IMPLEMENTED'; | |
| 326 }; | |
| 327 | |
| 328 /** | |
| 329 * Insert a string at the current cursor position. | |
| 330 * | |
| 331 * If the insert causes the column to overflow, the extra text is returned. | |
| 332 * | |
| 333 * @return {string} Text that overflowed the column, or null if nothing | |
| 334 * overflowed. | |
| 335 */ | |
| 336 hterm.Screen.prototype.insertString = function(str) { | |
| 337 if (this.cursorPosition.column == this.columnCount_) | |
| 338 return str; | |
| 339 | |
| 340 var totalRowText = this.cursorRowNode_.textContent; | |
| 341 | |
| 342 // There may not be underlying characters to support the current cursor | |
| 343 // position, since they don't get inserted until they're necessary. | |
| 344 var missingSpaceCount = Math.max(this.cursorPosition.column - | |
| 345 totalRowText.length, | |
| 346 0); | |
| 347 | |
| 348 var overflowCount = Math.max(totalRowText.length + missingSpaceCount + | |
| 349 str.length - this.columnCount_, | |
|
klimek
2011/11/24 19:13:51
Indent.
rginda
2011/11/28 20:39:47
Done.
| |
| 350 0); | |
| 351 | |
| 352 if (overflowCount > 0 && this.cursorNode_.nextSibling) { | |
| 353 // We're going to overflow, but there is text after the cursor with a | |
| 354 // different set of attributes. This is going to take some effort. | |
| 355 return this.spliceStringAndWrap_(str); | |
| 356 } | |
| 357 | |
| 358 // Wrapping is simple since the cursor is located in the last block of text | |
| 359 // on the line. | |
| 360 | |
| 361 var cursorNodeText = this.cursorNode_.textContent; | |
| 362 var leadingText = cursorNodeText.substr(0, this.cursorOffset_); | |
| 363 var trailingText = str + cursorNodeText.substr(this.cursorOffset_); | |
| 364 var overflowText = trailingText.substr(trailingText.length - overflowCount); | |
| 365 trailingText = trailingText.substr(0, trailingText.length - overflowCount); | |
| 366 | |
| 367 this.cursorNode_.textContent = ( | |
| 368 leadingText + | |
| 369 hterm.getWhitespace(missingSpaceCount) + | |
| 370 trailingText | |
| 371 ); | |
| 372 | |
| 373 var cursorDelta = Math.min(str.length, trailingText.length); | |
| 374 this.cursorOffset_ += cursorDelta; | |
| 375 this.cursorPosition.column += cursorDelta; | |
| 376 | |
| 377 return overflowText || null; | |
| 378 }; | |
| 379 | |
| 380 /** | |
| 381 * Overwrite the text at the current cursor position. | |
| 382 * | |
| 383 * If the text causes the column to overflow, the extra text is returned. | |
| 384 * | |
| 385 * @return {string} Text that overflowed the column, or null if nothing | |
| 386 * overflowed. | |
| 387 */ | |
| 388 hterm.Screen.prototype.overwriteString = function(str) { | |
| 389 var maxLength = this.columnCount_ - this.cursorPosition.column; | |
| 390 if (!maxLength) | |
| 391 return str; | |
| 392 | |
| 393 this.deleteChars(Math.min(str.length, maxLength)); | |
| 394 return this.insertString(str); | |
| 395 }; | |
| 396 | |
| 397 /** | |
| 398 * Forward-delete one or more characters at the current cursor position. | |
| 399 * | |
| 400 * Text to the right of the deleted characters is shifted left. Only affects | |
| 401 * characters on the same row as the cursor. | |
| 402 * | |
| 403 * @param {integer} count The number of characters to delete. This is clamped | |
| 404 * to the column width minus the cursor column. | |
| 405 */ | |
| 406 hterm.Screen.prototype.deleteChars = function(count) { | |
| 407 var node = this.cursorNode_; | |
| 408 var offset = this.cursorOffset_; | |
| 409 | |
| 410 while (node && count) { | |
| 411 var startLength = node.textContent.length; | |
| 412 | |
| 413 node.textContent = node.textContent.substr(0, offset) + | |
| 414 node.textContent.substr(offset + count); | |
| 415 | |
| 416 var endLength = node.textContent.length; | |
| 417 count -= startLength - endLength; | |
| 418 | |
| 419 if (endLength == 0 && node != this.cursorNode_) { | |
| 420 var nextNode = node.nextSibling; | |
| 421 node.parentNode.removeChild(node); | |
| 422 node = nextNode; | |
| 423 } else { | |
| 424 node = node.nextSibling; | |
| 425 } | |
| 426 | |
| 427 offset = 0; | |
| 428 } | |
| 429 }; | |
| OLD | NEW |