blob: 261df9670bff83e6354b0988769338b2905b5fcf [file] [log] [blame]
rginda87b86462011-12-14 13:48:03 -08001// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
rginda8ba33642011-12-14 12:31:31 -08002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
rgindacbbd7482012-06-13 15:06:16 -07005'use strict';
6
7lib.rtdep('lib.f',
8 'hterm.RowCol', 'hterm.Size', 'hterm.TextAttributes');
9
rginda8ba33642011-12-14 12:31:31 -080010/**
11 * @fileoverview This class represents a single terminal screen full of text.
12 *
13 * It maintains the current cursor position and has basic methods for text
14 * insert and overwrite, and adding or removing rows from the screen.
15 *
16 * This class has no knowledge of the scrollback buffer.
17 *
18 * The number of rows on the screen is determined only by the number of rows
19 * that the caller inserts into the screen. If a caller wants to ensure a
20 * constant number of rows on the screen, it's their responsibility to remove a
21 * row for each row inserted.
22 *
23 * The screen width, in contrast, is enforced locally.
24 *
25 *
26 * In practice...
27 * - The hterm.Terminal class holds two hterm.Screen instances. One for the
28 * primary screen and one for the alternate screen.
29 *
30 * - The html.Screen class only cares that rows are HTMLElements. In the
31 * larger context of hterm, however, the rows happen to be displayed by an
32 * hterm.ScrollPort and have to follow a few rules as a result. Each
33 * row must be rooted by the custom HTML tag 'x-row', and each must have a
34 * rowIndex property that corresponds to the index of the row in the context
35 * of the scrollback buffer. These invariants are enforced by hterm.Terminal
36 * because that is the class using the hterm.Screen in the context of an
37 * hterm.ScrollPort.
38 */
39
40/**
41 * Create a new screen instance.
42 *
43 * The screen initially has no rows and a maximum column count of 0.
44 *
45 * @param {integer} opt_columnCount The maximum number of columns for this
46 * screen. See insertString() and overwriteString() for information about
47 * what happens when too many characters are added too a row. Defaults to
48 * 0 if not provided.
49 */
50hterm.Screen = function(opt_columnCount) {
51 /**
52 * Public, read-only access to the rows in this screen.
53 */
54 this.rowsArray = [];
55
56 // The max column width for this screen.
rginda87b86462011-12-14 13:48:03 -080057 this.columnCount_ = opt_columnCount || 80;
rginda8ba33642011-12-14 12:31:31 -080058
rgindaa19afe22012-01-25 15:40:22 -080059 // The current color, bold, underline and blink attributes.
60 this.textAttributes = new hterm.TextAttributes(window.document);
61
rginda87b86462011-12-14 13:48:03 -080062 // Current zero-based cursor coordinates.
63 this.cursorPosition = new hterm.RowCol(0, 0);
rginda8ba33642011-12-14 12:31:31 -080064
65 // The node containing the row that the cursor is positioned on.
66 this.cursorRowNode_ = null;
67
68 // The node containing the span of text that the cursor is positioned on.
69 this.cursorNode_ = null;
70
71 // The offset into cursorNode_ where the cursor is positioned.
72 this.cursorOffset_ = null;
73};
74
75/**
76 * Return the screen size as an hterm.Size object.
77 *
78 * @return {hterm.Size} hterm.Size object representing the current number
79 * of rows and columns in this screen.
80 */
81hterm.Screen.prototype.getSize = function() {
82 return new hterm.Size(this.columnCount_, this.rowsArray.length);
83};
84
85/**
86 * Return the current number of rows in this screen.
87 *
88 * @return {integer} The number of rows in this screen.
89 */
90hterm.Screen.prototype.getHeight = function() {
91 return this.rowsArray.length;
92};
93
94/**
95 * Return the current number of columns in this screen.
96 *
97 * @return {integer} The number of columns in this screen.
98 */
99hterm.Screen.prototype.getWidth = function() {
100 return this.columnCount_;
101};
102
103/**
104 * Set the maximum number of columns per row.
105 *
rginda8ba33642011-12-14 12:31:31 -0800106 * @param {integer} count The maximum number of columns per row.
107 */
108hterm.Screen.prototype.setColumnCount = function(count) {
rginda2312fff2012-01-05 16:20:52 -0800109 this.columnCount_ = count;
110
rgindacbbd7482012-06-13 15:06:16 -0700111 if (this.cursorPosition.column >= count)
112 this.setCursorPosition(this.cursorPosition.row, count - 1);
rginda8ba33642011-12-14 12:31:31 -0800113};
114
115/**
116 * Remove the first row from the screen and return it.
117 *
118 * @return {HTMLElement} The first row in this screen.
119 */
120hterm.Screen.prototype.shiftRow = function() {
121 return this.shiftRows(1)[0];
rginda87b86462011-12-14 13:48:03 -0800122};
rginda8ba33642011-12-14 12:31:31 -0800123
124/**
125 * Remove rows from the top of the screen and return them as an array.
126 *
127 * @param {integer} count The number of rows to remove.
128 * @return {Array.<HTMLElement>} The selected rows.
129 */
130hterm.Screen.prototype.shiftRows = function(count) {
131 return this.rowsArray.splice(0, count);
132};
133
134/**
135 * Insert a row at the top of the screen.
136 *
137 * @param {HTMLElement} The row to insert.
138 */
139hterm.Screen.prototype.unshiftRow = function(row) {
140 this.rowsArray.splice(0, 0, row);
141};
142
143/**
144 * Insert rows at the top of the screen.
145 *
146 * @param {Array.<HTMLElement>} The rows to insert.
147 */
148hterm.Screen.prototype.unshiftRows = function(rows) {
149 this.rowsArray.unshift.apply(this.rowsArray, rows);
150};
151
152/**
153 * Remove the last row from the screen and return it.
154 *
155 * @return {HTMLElement} The last row in this screen.
156 */
157hterm.Screen.prototype.popRow = function() {
158 return this.popRows(1)[0];
159};
160
161/**
162 * Remove rows from the bottom of the screen and return them as an array.
163 *
164 * @param {integer} count The number of rows to remove.
165 * @return {Array.<HTMLElement>} The selected rows.
166 */
167hterm.Screen.prototype.popRows = function(count) {
168 return this.rowsArray.splice(this.rowsArray.length - count, count);
169};
170
171/**
172 * Insert a row at the bottom of the screen.
173 *
174 * @param {HTMLElement} The row to insert.
175 */
176hterm.Screen.prototype.pushRow = function(row) {
177 this.rowsArray.push(row);
178};
179
180/**
181 * Insert rows at the bottom of the screen.
182 *
183 * @param {Array.<HTMLElement>} The rows to insert.
184 */
185hterm.Screen.prototype.pushRows = function(rows) {
186 rows.push.apply(this.rowsArray, rows);
187};
188
189/**
190 * Insert a row at the specified column of the screen.
191 *
192 * @param {HTMLElement} The row to insert.
193 */
194hterm.Screen.prototype.insertRow = function(index, row) {
195 this.rowsArray.splice(index, 0, row);
196};
197
198/**
199 * Insert rows at the specified column of the screen.
200 *
201 * @param {Array.<HTMLElement>} The rows to insert.
202 */
203hterm.Screen.prototype.insertRows = function(index, rows) {
204 for (var i = 0; i < rows.length; i++) {
205 this.rowsArray.splice(index + i, 0, rows[i]);
206 }
207};
208
209/**
210 * Remove a last row from the specified column of the screen and return it.
211 *
212 * @return {HTMLElement} The selected row.
213 */
214hterm.Screen.prototype.removeRow = function(index) {
215 return this.rowsArray.splice(index, 1)[0];
216};
217
218/**
219 * Remove rows from the bottom of the screen and return them as an array.
220 *
221 * @param {integer} count The number of rows to remove.
222 * @return {Array.<HTMLElement>} The selected rows.
223 */
224hterm.Screen.prototype.removeRows = function(index, count) {
225 return this.rowsArray.splice(index, count);
226};
227
228/**
229 * Invalidate the current cursor position.
230 *
rginda87b86462011-12-14 13:48:03 -0800231 * This sets this.cursorPosition to (0, 0) and clears out some internal
rginda8ba33642011-12-14 12:31:31 -0800232 * data.
233 *
234 * Attempting to insert or overwrite text while the cursor position is invalid
235 * will raise an obscure exception.
236 */
237hterm.Screen.prototype.invalidateCursorPosition = function() {
rginda87b86462011-12-14 13:48:03 -0800238 this.cursorPosition.move(0, 0);
rginda8ba33642011-12-14 12:31:31 -0800239 this.cursorRowNode_ = null;
240 this.cursorNode_ = null;
241 this.cursorOffset_ = null;
242};
243
244/**
245 * Clear the contents of a selected row.
246 *
247 * TODO: Make this clear in the current style... somehow. We can't just
248 * fill the row with spaces, since they would have potential to mess up the
249 * terminal (for example, in insert mode, they might wrap around to the next
250 * line.
251 *
252 * @param {integer} index The zero-based index to clear.
253 */
254hterm.Screen.prototype.clearRow = function(index) {
255 if (index == this.cursorPosition.row) {
256 this.clearCursorRow();
257 } else {
258 var row = this.rowsArray[index];
259 row.innerHTML = '';
260 row.appendChild(row.ownerDocument.createTextNode(''));
261 }
262};
263
264/**
265 * Clear the contents of the cursor row.
266 *
267 * TODO: Same comment as clearRow().
268 */
269hterm.Screen.prototype.clearCursorRow = function() {
270 this.cursorRowNode_.innerHTML = '';
271 var text = this.cursorRowNode_.ownerDocument.createTextNode('');
272 this.cursorRowNode_.appendChild(text);
rgindaa09e7332012-08-17 12:49:51 -0700273 this.cursorRowNode_.removeAttribute('line-overflow');
rginda8ba33642011-12-14 12:31:31 -0800274 this.cursorOffset_ = 0;
275 this.cursorNode_ = text;
276 this.cursorPosition.column = 0;
rginda2312fff2012-01-05 16:20:52 -0800277 this.cursorPosition.overflow = false;
rginda8ba33642011-12-14 12:31:31 -0800278};
279
280/**
rgindaa09e7332012-08-17 12:49:51 -0700281 * Mark the current row as having overflowed to the next line.
282 *
283 * The line overflow state is used when converting a range of rows into text.
284 * It makes it possible to recombine two or more overflow terminal rows into
285 * a single line.
286 *
287 * This is distinct from the cursor being in the overflow state. Cursor
288 * overflow indicates that printing at the cursor position will commit a
289 * line overflow, unless it is preceded by a repositioning of the cursor
290 * to a non-overflow state.
291 */
292hterm.Screen.prototype.commitLineOverflow = function() {
293 this.cursorRowNode_.setAttribute('line-overflow', true);
294};
295
296/**
rginda8ba33642011-12-14 12:31:31 -0800297 * Relocate the cursor to a give row and column.
298 *
299 * @param {integer} row The zero based row.
300 * @param {integer} column The zero based column.
301 */
302hterm.Screen.prototype.setCursorPosition = function(row, column) {
rginda11057d52012-04-25 12:29:56 -0700303 if (!this.rowsArray.length) {
304 console.warn('Attempt to set cursor position on empty screen.');
305 return;
306 }
307
rginda87b86462011-12-14 13:48:03 -0800308 if (row >= this.rowsArray.length) {
rgindacbbd7482012-06-13 15:06:16 -0700309 console.error('Row out of bounds: ' + row);
rginda87b86462011-12-14 13:48:03 -0800310 row = this.rowsArray.length - 1;
311 } else if (row < 0) {
rgindacbbd7482012-06-13 15:06:16 -0700312 console.error('Row out of bounds: ' + row);
rginda87b86462011-12-14 13:48:03 -0800313 row = 0;
314 }
315
316 if (column >= this.columnCount_) {
rgindacbbd7482012-06-13 15:06:16 -0700317 console.error('Column out of bounds: ' + column);
rginda87b86462011-12-14 13:48:03 -0800318 column = this.columnCount_ - 1;
319 } else if (column < 0) {
rgindacbbd7482012-06-13 15:06:16 -0700320 console.error('Column out of bounds: ' + column);
rginda87b86462011-12-14 13:48:03 -0800321 column = 0;
322 }
rginda8ba33642011-12-14 12:31:31 -0800323
rginda2312fff2012-01-05 16:20:52 -0800324 this.cursorPosition.overflow = false;
325
rginda8ba33642011-12-14 12:31:31 -0800326 var rowNode = this.rowsArray[row];
327 var node = rowNode.firstChild;
328
329 if (!node) {
330 node = rowNode.ownerDocument.createTextNode('');
331 rowNode.appendChild(node);
332 }
333
rgindaa19afe22012-01-25 15:40:22 -0800334 var currentColumn = 0;
335
rginda8ba33642011-12-14 12:31:31 -0800336 if (rowNode == this.cursorRowNode_) {
337 if (column >= this.cursorPosition.column - this.cursorOffset_) {
338 node = this.cursorNode_;
339 currentColumn = this.cursorPosition.column - this.cursorOffset_;
340 }
341 } else {
342 this.cursorRowNode_ = rowNode;
343 }
344
345 this.cursorPosition.move(row, column);
346
347 while (node) {
348 var offset = column - currentColumn;
349 var textContent = node.textContent;
350 if (!node.nextSibling || textContent.length > offset) {
351 this.cursorNode_ = node;
352 this.cursorOffset_ = offset;
353 return;
354 }
355
356 currentColumn += textContent.length;
357 node = node.nextSibling;
358 }
359};
360
361/**
rginda87b86462011-12-14 13:48:03 -0800362 * Set the provided selection object to be a caret selection at the current
363 * cursor position.
364 */
365hterm.Screen.prototype.syncSelectionCaret = function(selection) {
366 selection.collapse(this.cursorNode_, this.cursorOffset_);
367};
368
369/**
rgindaa19afe22012-01-25 15:40:22 -0800370 * Split a single node into two nodes at the given offset.
rginda8ba33642011-12-14 12:31:31 -0800371 *
rgindaa19afe22012-01-25 15:40:22 -0800372 * For example:
373 * Given the DOM fragment '<div><span>Hello World</span></div>', call splitNode_
374 * passing the span and an offset of 6. This would modifiy the fragment to
375 * become: '<div><span>Hello </span><span>World</span></div>'. If the span
376 * had any attributes they would have been copied to the new span as well.
377 *
378 * The to-be-split node must have a container, so that the new node can be
379 * placed next to it.
380 *
381 * @param {HTMLNode} node The node to split.
382 * @param {integer} offset The offset into the node where the split should
383 * occur.
rginda8ba33642011-12-14 12:31:31 -0800384 */
rgindaa19afe22012-01-25 15:40:22 -0800385hterm.Screen.prototype.splitNode_ = function(node, offset) {
rginda35c456b2012-02-09 17:29:05 -0800386 var afterNode = node.cloneNode(false);
rgindaa19afe22012-01-25 15:40:22 -0800387
rginda35c456b2012-02-09 17:29:05 -0800388 var textContent = node.textContent;
389 node.textContent = textContent.substr(0, offset);
390 afterNode.textContent = textContent.substr(offset);
rgindaa19afe22012-01-25 15:40:22 -0800391
392 node.parentNode.insertBefore(afterNode, node.nextSibling);
rginda8ba33642011-12-14 12:31:31 -0800393};
394
395/**
rgindaa9abdd82012-08-06 18:05:09 -0700396 * Ensure that text is clipped and the cursor is clamped to the column count.
rgindaa19afe22012-01-25 15:40:22 -0800397 */
rgindaa9abdd82012-08-06 18:05:09 -0700398hterm.Screen.prototype.maybeClipCurrentRow = function() {
399 if (this.cursorRowNode_.textContent.length <= this.columnCount_) {
400 // Current row does not need clipping, but may need clamping.
401 if (this.cursorPosition.column >= this.columnCount_) {
402 this.setCursorPosition(this.cursorPosition.row, this.columnCount_ - 1);
403 this.cursorPosition.overflow = true;
404 }
rgindaa19afe22012-01-25 15:40:22 -0800405
rgindaa9abdd82012-08-06 18:05:09 -0700406 return;
407 }
408
409 // Save off the current column so we can maybe restore it later.
410 var currentColumn = this.cursorPosition.column;
411
412 // Move the cursor to the final column.
413 this.setCursorPosition(this.cursorPosition.row, this.columnCount_ - 1);
414
415 // Remove any text that partially overflows.
416 var cursorNodeText = this.cursorNode_.textContent;
417 if (this.cursorOffset_ < cursorNodeText.length - 1) {
418 this.cursorNode_.textContent = cursorNodeText.substr(
419 0, this.cursorOffset_ + 1);
420 }
421
422 // Remove all nodes after the cursor.
rgindaa19afe22012-01-25 15:40:22 -0800423 var rowNode = this.cursorRowNode_;
424 var node = this.cursorNode_.nextSibling;
425
426 while (node) {
rgindaa19afe22012-01-25 15:40:22 -0800427 rowNode.removeChild(node);
428 node = this.cursorNode_.nextSibling;
429 }
430
rgindaa9abdd82012-08-06 18:05:09 -0700431 if (currentColumn < this.columnCount_ - 1) {
432 // If the cursor was within the screen before we started then restore its
433 // position.
rgindaa19afe22012-01-25 15:40:22 -0800434 this.setCursorPosition(this.cursorPosition.row, currentColumn);
rgindaa9abdd82012-08-06 18:05:09 -0700435 } else {
436 // Otherwise leave it at the the last column in the overflow state.
437 this.cursorPosition.overflow = true;
rgindaa19afe22012-01-25 15:40:22 -0800438 }
rgindaa19afe22012-01-25 15:40:22 -0800439};
440
441/**
442 * Insert a string at the current character position using the current
443 * text attributes.
444 *
rgindaa09e7332012-08-17 12:49:51 -0700445 * You must call maybeClipCurrentRow() after in order to clip overflowed
446 * text and clamp the cursor.
447 *
448 * It is also up to the caller to properly maintain the line overflow state
449 * using hterm.Screen..commitLineOverflow().
rginda8ba33642011-12-14 12:31:31 -0800450 */
451hterm.Screen.prototype.insertString = function(str) {
rgindaa19afe22012-01-25 15:40:22 -0800452 var cursorNode = this.cursorNode_;
453 var cursorNodeText = cursorNode.textContent;
rginda8ba33642011-12-14 12:31:31 -0800454
rgindaa19afe22012-01-25 15:40:22 -0800455 // We may alter the length of the string by prepending some missing
456 // whitespace, so we need to record the string length ahead of time.
457 var strLength = str.length;
rginda8ba33642011-12-14 12:31:31 -0800458
rgindaa19afe22012-01-25 15:40:22 -0800459 // No matter what, before this function exits the cursor column will have
460 // moved this much.
461 this.cursorPosition.column += strLength;
rginda8ba33642011-12-14 12:31:31 -0800462
rgindaa19afe22012-01-25 15:40:22 -0800463 // Local cache of the cursor offset.
464 var offset = this.cursorOffset_;
rginda8ba33642011-12-14 12:31:31 -0800465
rgindaa19afe22012-01-25 15:40:22 -0800466 // Reverse offset is the offset measured from the end of the string.
467 // Zero implies that the cursor is at the end of the cursor node.
468 var reverseOffset = cursorNodeText.length - offset
469
470 if (reverseOffset < 0) {
471 // A negative reverse offset means the cursor is positioned past the end
472 // of the characters on this line. We'll need to insert the missing
473 // whitespace.
rgindacbbd7482012-06-13 15:06:16 -0700474 var ws = lib.f.getWhitespace(-reverseOffset);
rgindaa19afe22012-01-25 15:40:22 -0800475
476 // This whitespace should be completely unstyled. Underline and background
477 // color would be visible on whitespace, so we can't use one of those
478 // spans to hold the text.
479 if (!(this.textAttributes.underline || this.textAttributes.background)) {
480 // Best case scenario, we can just pretend the spaces were part of the
481 // original string.
482 str = ws + str;
483 } else if (cursorNode.nodeType == 3 ||
484 !(cursorNode.style.textDecoration ||
485 cursorNode.style.backgroundColor)) {
486 // Second best case, the current node is able to hold the whitespace.
487 cursorNode.textContent = (cursorNodeText += ws);
488 } else {
489 // Worst case, we have to create a new node to hold the whitespace.
490 var wsNode = cursorNode.ownerDocument.createTextNode(ws);
491 this.cursorRowNode_.insertBefore(wsNode, cursorNode.nextSibling);
492 this.cursorNode_ = cursorNode = wsNode;
493 this.cursorOffset_ = offset = -reverseOffset;
494 cursorNodeText = ws;
495 }
496
497 // We now know for sure that we're at the last character of the cursor node.
498 reverseOffset = 0;
rginda8ba33642011-12-14 12:31:31 -0800499 }
500
rgindaa19afe22012-01-25 15:40:22 -0800501 if (this.textAttributes.matchesContainer(cursorNode)) {
502 // The new text can be placed directly in the cursor node.
503 if (reverseOffset == 0) {
504 cursorNode.textContent = cursorNodeText + str;
505 } else if (offset == 0) {
506 cursorNode.textContent = str + cursorNodeText;
507 } else {
508 cursorNode.textContent = cursorNodeText.substr(0, offset) + str +
509 cursorNodeText.substr(offset);
510 }
rginda8ba33642011-12-14 12:31:31 -0800511
rgindaa19afe22012-01-25 15:40:22 -0800512 this.cursorOffset_ += strLength;
513 return;
rginda87b86462011-12-14 13:48:03 -0800514 }
515
rgindaa19afe22012-01-25 15:40:22 -0800516 // The cursor node is the wrong style for the new text. If we're at the
517 // beginning or end of the cursor node, then the adjacent node is also a
518 // potential candidate.
rginda8ba33642011-12-14 12:31:31 -0800519
rgindaa19afe22012-01-25 15:40:22 -0800520 if (offset == 0) {
521 // At the beginning of the cursor node, the check the previous sibling.
522 var previousSibling = cursorNode.previousSibling;
523 if (previousSibling &&
524 this.textAttributes.matchesContainer(previousSibling)) {
525 previousSibling.textContent += str;
526 this.cursorNode_ = previousSibling;
527 this.cursorOffset_ = previousSibling.textContent.length;
528 return;
529 }
530
531 var newNode = this.textAttributes.createContainer(str);
532 this.cursorRowNode_.insertBefore(newNode, cursorNode);
533 this.cursorNode_ = newNode;
534 this.cursorOffset_ = strLength;
535 return;
536 }
537
538 if (reverseOffset == 0) {
539 // At the end of the cursor node, the check the next sibling.
540 var nextSibling = cursorNode.nextSibling;
541 if (nextSibling &&
542 this.textAttributes.matchesContainer(nextSibling)) {
543 nextSibling.textContent = str + nextSibling.textContent;
544 this.cursorNode_ = nextSibling;
545 this.cursorOffset_ = strLength;
546 return;
547 }
548
549 var newNode = this.textAttributes.createContainer(str);
550 this.cursorRowNode_.insertBefore(newNode, nextSibling);
551 this.cursorNode_ = newNode;
552 // We specifically need to include any missing whitespace here, since it's
553 // going in a new node.
554 this.cursorOffset_ = str.length;
555 return;
556 }
557
558 // Worst case, we're somewhere in the middle of the cursor node. We'll
559 // have to split it into two nodes and insert our new container in between.
560 this.splitNode_(cursorNode, offset);
561 var newNode = this.textAttributes.createContainer(str);
562 this.cursorRowNode_.insertBefore(newNode, cursorNode.nextSibling);
563 this.cursorNode_ = newNode;
564 this.cursorOffset_ = strLength;
565};
566
567/**
rginda8ba33642011-12-14 12:31:31 -0800568 * Overwrite the text at the current cursor position.
569 *
rgindaa09e7332012-08-17 12:49:51 -0700570 *
571 * You must call maybeClipCurrentRow() after in order to clip overflowed
572 * text and clamp the cursor.
573 *
574 * It is also up to the caller to properly maintain the line overflow state
575 * using hterm.Screen..commitLineOverflow().
rginda8ba33642011-12-14 12:31:31 -0800576 */
577hterm.Screen.prototype.overwriteString = function(str) {
578 var maxLength = this.columnCount_ - this.cursorPosition.column;
579 if (!maxLength)
rgindaa19afe22012-01-25 15:40:22 -0800580 return [str];
581
582 if ((this.cursorNode_.textContent.substr(this.cursorOffset_) == str) &&
583 this.textAttributes.matchesContainer(this.cursorNode_)) {
584 // This overwrite would be a no-op, just move the cursor and return.
585 this.cursorOffset_ += str.length;
586 this.cursorPosition.column += str.length;
587 return;
588 }
rginda8ba33642011-12-14 12:31:31 -0800589
590 this.deleteChars(Math.min(str.length, maxLength));
rgindaa19afe22012-01-25 15:40:22 -0800591 this.insertString(str);
rginda8ba33642011-12-14 12:31:31 -0800592};
593
594/**
595 * Forward-delete one or more characters at the current cursor position.
596 *
597 * Text to the right of the deleted characters is shifted left. Only affects
598 * characters on the same row as the cursor.
599 *
600 * @param {integer} count The number of characters to delete. This is clamped
601 * to the column width minus the cursor column.
602 */
603hterm.Screen.prototype.deleteChars = function(count) {
604 var node = this.cursorNode_;
605 var offset = this.cursorOffset_;
rginda8e92a692012-05-20 19:37:20 -0700606 var textContent = node.textContent;
rginda8ba33642011-12-14 12:31:31 -0800607
rginda8e92a692012-05-20 19:37:20 -0700608 if (textContent.length <= offset && !node.nextSibling) {
rgindaa19afe22012-01-25 15:40:22 -0800609 // There's nothing after this node/offset to delete, buh bye.
610 return;
611 }
612
rginda8ba33642011-12-14 12:31:31 -0800613 while (node && count) {
rginda8e92a692012-05-20 19:37:20 -0700614 var startLength = textContent.length;
rginda8ba33642011-12-14 12:31:31 -0800615
rginda8e92a692012-05-20 19:37:20 -0700616 textContent = textContent.substr(0, offset) +
617 textContent.substr(offset + count);
rginda8ba33642011-12-14 12:31:31 -0800618
rginda8e92a692012-05-20 19:37:20 -0700619 var endLength = textContent.length;
620
rginda8ba33642011-12-14 12:31:31 -0800621 count -= startLength - endLength;
622
rginda8e92a692012-05-20 19:37:20 -0700623 node.textContent = textContent;
624
rginda8ba33642011-12-14 12:31:31 -0800625 if (endLength == 0 && node != this.cursorNode_) {
626 var nextNode = node.nextSibling;
627 node.parentNode.removeChild(node);
628 node = nextNode;
629 } else {
630 node = node.nextSibling;
631 }
632
rginda8e92a692012-05-20 19:37:20 -0700633 if (node)
634 textContent = node.textContent;
rginda8ba33642011-12-14 12:31:31 -0800635 offset = 0;
636 }
637};