blob: 6d60db18e3061aeabd9ba99c0d644b081a95c08d [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
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 */
45hterm.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.
rginda87b86462011-12-14 13:48:03 -080052 this.columnCount_ = opt_columnCount || 80;
rginda8ba33642011-12-14 12:31:31 -080053
rgindaa19afe22012-01-25 15:40:22 -080054 // The current color, bold, underline and blink attributes.
55 this.textAttributes = new hterm.TextAttributes(window.document);
56
rginda87b86462011-12-14 13:48:03 -080057 // Current zero-based cursor coordinates.
58 this.cursorPosition = new hterm.RowCol(0, 0);
rginda8ba33642011-12-14 12:31:31 -080059
60 // The node containing the row that the cursor is positioned on.
61 this.cursorRowNode_ = null;
62
63 // The node containing the span of text that the cursor is positioned on.
64 this.cursorNode_ = null;
65
66 // The offset into cursorNode_ where the cursor is positioned.
67 this.cursorOffset_ = null;
68};
69
70/**
71 * Return the screen size as an hterm.Size object.
72 *
73 * @return {hterm.Size} hterm.Size object representing the current number
74 * of rows and columns in this screen.
75 */
76hterm.Screen.prototype.getSize = function() {
77 return new hterm.Size(this.columnCount_, this.rowsArray.length);
78};
79
80/**
81 * Return the current number of rows in this screen.
82 *
83 * @return {integer} The number of rows in this screen.
84 */
85hterm.Screen.prototype.getHeight = function() {
86 return this.rowsArray.length;
87};
88
89/**
90 * Return the current number of columns in this screen.
91 *
92 * @return {integer} The number of columns in this screen.
93 */
94hterm.Screen.prototype.getWidth = function() {
95 return this.columnCount_;
96};
97
98/**
99 * Set the maximum number of columns per row.
100 *
rginda8ba33642011-12-14 12:31:31 -0800101 * @param {integer} count The maximum number of columns per row.
102 */
103hterm.Screen.prototype.setColumnCount = function(count) {
rginda2312fff2012-01-05 16:20:52 -0800104 this.columnCount_ = count;
105
rginda35c456b2012-02-09 17:29:05 -0800106 if (this.cursorPosition.column >= count) {
107 this.setCursorPosition(this.cursorPosition.row,
108 this.cursorPosition.column - 1);
rginda87b86462011-12-14 13:48:03 -0800109 }
rginda8ba33642011-12-14 12:31:31 -0800110};
111
112/**
113 * Remove the first row from the screen and return it.
114 *
115 * @return {HTMLElement} The first row in this screen.
116 */
117hterm.Screen.prototype.shiftRow = function() {
118 return this.shiftRows(1)[0];
rginda87b86462011-12-14 13:48:03 -0800119};
rginda8ba33642011-12-14 12:31:31 -0800120
121/**
122 * Remove rows from the top of the screen and return them as an array.
123 *
124 * @param {integer} count The number of rows to remove.
125 * @return {Array.<HTMLElement>} The selected rows.
126 */
127hterm.Screen.prototype.shiftRows = function(count) {
128 return this.rowsArray.splice(0, count);
129};
130
131/**
132 * Insert a row at the top of the screen.
133 *
134 * @param {HTMLElement} The row to insert.
135 */
136hterm.Screen.prototype.unshiftRow = function(row) {
137 this.rowsArray.splice(0, 0, row);
138};
139
140/**
141 * Insert rows at the top of the screen.
142 *
143 * @param {Array.<HTMLElement>} The rows to insert.
144 */
145hterm.Screen.prototype.unshiftRows = function(rows) {
146 this.rowsArray.unshift.apply(this.rowsArray, rows);
147};
148
149/**
150 * Remove the last row from the screen and return it.
151 *
152 * @return {HTMLElement} The last row in this screen.
153 */
154hterm.Screen.prototype.popRow = function() {
155 return this.popRows(1)[0];
156};
157
158/**
159 * Remove rows from the bottom of the screen and return them as an array.
160 *
161 * @param {integer} count The number of rows to remove.
162 * @return {Array.<HTMLElement>} The selected rows.
163 */
164hterm.Screen.prototype.popRows = function(count) {
165 return this.rowsArray.splice(this.rowsArray.length - count, count);
166};
167
168/**
169 * Insert a row at the bottom of the screen.
170 *
171 * @param {HTMLElement} The row to insert.
172 */
173hterm.Screen.prototype.pushRow = function(row) {
174 this.rowsArray.push(row);
175};
176
177/**
178 * Insert rows at the bottom of the screen.
179 *
180 * @param {Array.<HTMLElement>} The rows to insert.
181 */
182hterm.Screen.prototype.pushRows = function(rows) {
183 rows.push.apply(this.rowsArray, rows);
184};
185
186/**
187 * Insert a row at the specified column of the screen.
188 *
189 * @param {HTMLElement} The row to insert.
190 */
191hterm.Screen.prototype.insertRow = function(index, row) {
192 this.rowsArray.splice(index, 0, row);
193};
194
195/**
196 * Insert rows at the specified column of the screen.
197 *
198 * @param {Array.<HTMLElement>} The rows to insert.
199 */
200hterm.Screen.prototype.insertRows = function(index, rows) {
201 for (var i = 0; i < rows.length; i++) {
202 this.rowsArray.splice(index + i, 0, rows[i]);
203 }
204};
205
206/**
207 * Remove a last row from the specified column of the screen and return it.
208 *
209 * @return {HTMLElement} The selected row.
210 */
211hterm.Screen.prototype.removeRow = function(index) {
212 return this.rowsArray.splice(index, 1)[0];
213};
214
215/**
216 * Remove rows from the bottom of the screen and return them as an array.
217 *
218 * @param {integer} count The number of rows to remove.
219 * @return {Array.<HTMLElement>} The selected rows.
220 */
221hterm.Screen.prototype.removeRows = function(index, count) {
222 return this.rowsArray.splice(index, count);
223};
224
225/**
226 * Invalidate the current cursor position.
227 *
rginda87b86462011-12-14 13:48:03 -0800228 * This sets this.cursorPosition to (0, 0) and clears out some internal
rginda8ba33642011-12-14 12:31:31 -0800229 * data.
230 *
231 * Attempting to insert or overwrite text while the cursor position is invalid
232 * will raise an obscure exception.
233 */
234hterm.Screen.prototype.invalidateCursorPosition = function() {
rginda87b86462011-12-14 13:48:03 -0800235 this.cursorPosition.move(0, 0);
rginda8ba33642011-12-14 12:31:31 -0800236 this.cursorRowNode_ = null;
237 this.cursorNode_ = null;
238 this.cursorOffset_ = null;
239};
240
241/**
242 * Clear the contents of a selected row.
243 *
244 * TODO: Make this clear in the current style... somehow. We can't just
245 * fill the row with spaces, since they would have potential to mess up the
246 * terminal (for example, in insert mode, they might wrap around to the next
247 * line.
248 *
249 * @param {integer} index The zero-based index to clear.
250 */
251hterm.Screen.prototype.clearRow = function(index) {
252 if (index == this.cursorPosition.row) {
253 this.clearCursorRow();
254 } else {
255 var row = this.rowsArray[index];
256 row.innerHTML = '';
257 row.appendChild(row.ownerDocument.createTextNode(''));
258 }
259};
260
261/**
262 * Clear the contents of the cursor row.
263 *
264 * TODO: Same comment as clearRow().
265 */
266hterm.Screen.prototype.clearCursorRow = function() {
267 this.cursorRowNode_.innerHTML = '';
268 var text = this.cursorRowNode_.ownerDocument.createTextNode('');
269 this.cursorRowNode_.appendChild(text);
270 this.cursorOffset_ = 0;
271 this.cursorNode_ = text;
272 this.cursorPosition.column = 0;
rginda2312fff2012-01-05 16:20:52 -0800273 this.cursorPosition.overflow = false;
rginda8ba33642011-12-14 12:31:31 -0800274};
275
276/**
277 * Relocate the cursor to a give row and column.
278 *
279 * @param {integer} row The zero based row.
280 * @param {integer} column The zero based column.
281 */
282hterm.Screen.prototype.setCursorPosition = function(row, column) {
rginda11057d52012-04-25 12:29:56 -0700283 if (!this.rowsArray.length) {
284 console.warn('Attempt to set cursor position on empty screen.');
285 return;
286 }
287
rginda87b86462011-12-14 13:48:03 -0800288 if (row >= this.rowsArray.length) {
rginda11057d52012-04-25 12:29:56 -0700289 console.warn('Row out of bounds: ' + row, hterm.getStack(1));
rginda87b86462011-12-14 13:48:03 -0800290 row = this.rowsArray.length - 1;
291 } else if (row < 0) {
rginda11057d52012-04-25 12:29:56 -0700292 console.warn('Row out of bounds: ' + row, hterm.getStack(1));
rginda87b86462011-12-14 13:48:03 -0800293 row = 0;
294 }
295
296 if (column >= this.columnCount_) {
297 console.log('Column out of bounds: ' + column, hterm.getStack(1));
298 column = this.columnCount_ - 1;
299 } else if (column < 0) {
300 console.log('Column out of bounds: ' + column, hterm.getStack(1));
301 column = 0;
302 }
rginda8ba33642011-12-14 12:31:31 -0800303
rginda2312fff2012-01-05 16:20:52 -0800304 this.cursorPosition.overflow = false;
305
rginda8ba33642011-12-14 12:31:31 -0800306 var rowNode = this.rowsArray[row];
307 var node = rowNode.firstChild;
308
309 if (!node) {
310 node = rowNode.ownerDocument.createTextNode('');
311 rowNode.appendChild(node);
312 }
313
rgindaa19afe22012-01-25 15:40:22 -0800314 var currentColumn = 0;
315
rginda8ba33642011-12-14 12:31:31 -0800316 if (rowNode == this.cursorRowNode_) {
317 if (column >= this.cursorPosition.column - this.cursorOffset_) {
318 node = this.cursorNode_;
319 currentColumn = this.cursorPosition.column - this.cursorOffset_;
320 }
321 } else {
322 this.cursorRowNode_ = rowNode;
323 }
324
325 this.cursorPosition.move(row, column);
326
327 while (node) {
328 var offset = column - currentColumn;
329 var textContent = node.textContent;
330 if (!node.nextSibling || textContent.length > offset) {
331 this.cursorNode_ = node;
332 this.cursorOffset_ = offset;
333 return;
334 }
335
336 currentColumn += textContent.length;
337 node = node.nextSibling;
338 }
339};
340
341/**
rginda87b86462011-12-14 13:48:03 -0800342 * Set the provided selection object to be a caret selection at the current
343 * cursor position.
344 */
345hterm.Screen.prototype.syncSelectionCaret = function(selection) {
346 selection.collapse(this.cursorNode_, this.cursorOffset_);
347};
348
349/**
rgindaa19afe22012-01-25 15:40:22 -0800350 * Split a single node into two nodes at the given offset.
rginda8ba33642011-12-14 12:31:31 -0800351 *
rgindaa19afe22012-01-25 15:40:22 -0800352 * For example:
353 * Given the DOM fragment '<div><span>Hello World</span></div>', call splitNode_
354 * passing the span and an offset of 6. This would modifiy the fragment to
355 * become: '<div><span>Hello </span><span>World</span></div>'. If the span
356 * had any attributes they would have been copied to the new span as well.
357 *
358 * The to-be-split node must have a container, so that the new node can be
359 * placed next to it.
360 *
361 * @param {HTMLNode} node The node to split.
362 * @param {integer} offset The offset into the node where the split should
363 * occur.
rginda8ba33642011-12-14 12:31:31 -0800364 */
rgindaa19afe22012-01-25 15:40:22 -0800365hterm.Screen.prototype.splitNode_ = function(node, offset) {
rginda35c456b2012-02-09 17:29:05 -0800366 var afterNode = node.cloneNode(false);
rgindaa19afe22012-01-25 15:40:22 -0800367
rginda35c456b2012-02-09 17:29:05 -0800368 var textContent = node.textContent;
369 node.textContent = textContent.substr(0, offset);
370 afterNode.textContent = textContent.substr(offset);
rgindaa19afe22012-01-25 15:40:22 -0800371
372 node.parentNode.insertBefore(afterNode, node.nextSibling);
rginda8ba33642011-12-14 12:31:31 -0800373};
374
375/**
rgindaa19afe22012-01-25 15:40:22 -0800376 * Remove and return all content past the end of the current cursor position.
rginda8ba33642011-12-14 12:31:31 -0800377 *
rgindaa19afe22012-01-25 15:40:22 -0800378 * If necessary, the cursor's current node will be split. Everything past
379 * the end of the cursor will be returned in an array. Any empty nodes
380 * will be omitted from the result array. If the resulting array is empty,
381 * this function will return null.
rginda8ba33642011-12-14 12:31:31 -0800382 *
rgindaa19afe22012-01-25 15:40:22 -0800383 * @return {Array} An array of DOM nodes that used to appear after the cursor,
384 * or null if the cursor was already at the end of the line.
385 */
386hterm.Screen.prototype.clipAtCursor_ = function() {
387 if (this.cursorOffset_ < this.cursorNode_.textContent.length - 1)
388 this.splitNode_(this.cursorNode_, this.cursorOffset_ + 1);
389
390 var rv = null;
391 var rowNode = this.cursorRowNode_;
392 var node = this.cursorNode_.nextSibling;
393
394 while (node) {
395 var length = node.textContent.length;
396 if (length) {
397 if (rv) {
398 rv.push(node);
399 rv.characterLength += length;
400 } else {
401 rv = [node];
402 rv.characterLength = length;
403 }
404 }
405
406 rowNode.removeChild(node);
407 node = this.cursorNode_.nextSibling;
408 }
409
410 return rv;
411};
412
413/**
414 * Ensure that the current row does not overflow the current column count.
415 *
416 * If the current row is too long, it will be clipped and the overflow content
417 * will be returned as an array of DOM nodes. Otherwise this function returns
418 * null.
419 *
420 * @return {Array} An array of DOM nodes that overflowed in the current row,
421 * or null if the row did not overflow.
422 */
423hterm.Screen.prototype.maybeClipCurrentRow = function() {
424 var currentColumn = this.cursorPosition.column;
425
426 if (currentColumn >= this.columnCount_) {
427 this.setCursorPosition(this.cursorPosition.row, this.columnCount_ - 1);
428 this.cursorPosition.overflow = true;
429 return this.clipAtCursor_();
430 }
431
432 if (this.cursorRowNode_.textContent.length > this.columnCount_) {
433 this.setCursorPosition(this.cursorPosition.row, this.columnCount_ - 1);
434 var overflow = this.clipAtCursor_();
435 this.setCursorPosition(this.cursorPosition.row, currentColumn);
436 return overflow;
437 }
438
439 return null;
440};
441
442/**
443 * Insert a string at the current character position using the current
444 * text attributes.
445 *
446 * You must call maybeClipCurrentRow() after in order to check overflow.
rginda8ba33642011-12-14 12:31:31 -0800447 */
448hterm.Screen.prototype.insertString = function(str) {
rgindaa19afe22012-01-25 15:40:22 -0800449 var cursorNode = this.cursorNode_;
450 var cursorNodeText = cursorNode.textContent;
rginda8ba33642011-12-14 12:31:31 -0800451
rgindaa19afe22012-01-25 15:40:22 -0800452 // We may alter the length of the string by prepending some missing
453 // whitespace, so we need to record the string length ahead of time.
454 var strLength = str.length;
rginda8ba33642011-12-14 12:31:31 -0800455
rgindaa19afe22012-01-25 15:40:22 -0800456 // No matter what, before this function exits the cursor column will have
457 // moved this much.
458 this.cursorPosition.column += strLength;
rginda8ba33642011-12-14 12:31:31 -0800459
rgindaa19afe22012-01-25 15:40:22 -0800460 // Local cache of the cursor offset.
461 var offset = this.cursorOffset_;
rginda8ba33642011-12-14 12:31:31 -0800462
rgindaa19afe22012-01-25 15:40:22 -0800463 // Reverse offset is the offset measured from the end of the string.
464 // Zero implies that the cursor is at the end of the cursor node.
465 var reverseOffset = cursorNodeText.length - offset
466
467 if (reverseOffset < 0) {
468 // A negative reverse offset means the cursor is positioned past the end
469 // of the characters on this line. We'll need to insert the missing
470 // whitespace.
471 var ws = hterm.getWhitespace(-reverseOffset);
472
473 // This whitespace should be completely unstyled. Underline and background
474 // color would be visible on whitespace, so we can't use one of those
475 // spans to hold the text.
476 if (!(this.textAttributes.underline || this.textAttributes.background)) {
477 // Best case scenario, we can just pretend the spaces were part of the
478 // original string.
479 str = ws + str;
480 } else if (cursorNode.nodeType == 3 ||
481 !(cursorNode.style.textDecoration ||
482 cursorNode.style.backgroundColor)) {
483 // Second best case, the current node is able to hold the whitespace.
484 cursorNode.textContent = (cursorNodeText += ws);
485 } else {
486 // Worst case, we have to create a new node to hold the whitespace.
487 var wsNode = cursorNode.ownerDocument.createTextNode(ws);
488 this.cursorRowNode_.insertBefore(wsNode, cursorNode.nextSibling);
489 this.cursorNode_ = cursorNode = wsNode;
490 this.cursorOffset_ = offset = -reverseOffset;
491 cursorNodeText = ws;
492 }
493
494 // We now know for sure that we're at the last character of the cursor node.
495 reverseOffset = 0;
rginda8ba33642011-12-14 12:31:31 -0800496 }
497
rgindaa19afe22012-01-25 15:40:22 -0800498 if (this.textAttributes.matchesContainer(cursorNode)) {
499 // The new text can be placed directly in the cursor node.
500 if (reverseOffset == 0) {
501 cursorNode.textContent = cursorNodeText + str;
502 } else if (offset == 0) {
503 cursorNode.textContent = str + cursorNodeText;
504 } else {
505 cursorNode.textContent = cursorNodeText.substr(0, offset) + str +
506 cursorNodeText.substr(offset);
507 }
rginda8ba33642011-12-14 12:31:31 -0800508
rgindaa19afe22012-01-25 15:40:22 -0800509 this.cursorOffset_ += strLength;
510 return;
rginda87b86462011-12-14 13:48:03 -0800511 }
512
rgindaa19afe22012-01-25 15:40:22 -0800513 // The cursor node is the wrong style for the new text. If we're at the
514 // beginning or end of the cursor node, then the adjacent node is also a
515 // potential candidate.
rginda8ba33642011-12-14 12:31:31 -0800516
rgindaa19afe22012-01-25 15:40:22 -0800517 if (offset == 0) {
518 // At the beginning of the cursor node, the check the previous sibling.
519 var previousSibling = cursorNode.previousSibling;
520 if (previousSibling &&
521 this.textAttributes.matchesContainer(previousSibling)) {
522 previousSibling.textContent += str;
523 this.cursorNode_ = previousSibling;
524 this.cursorOffset_ = previousSibling.textContent.length;
525 return;
526 }
527
528 var newNode = this.textAttributes.createContainer(str);
529 this.cursorRowNode_.insertBefore(newNode, cursorNode);
530 this.cursorNode_ = newNode;
531 this.cursorOffset_ = strLength;
532 return;
533 }
534
535 if (reverseOffset == 0) {
536 // At the end of the cursor node, the check the next sibling.
537 var nextSibling = cursorNode.nextSibling;
538 if (nextSibling &&
539 this.textAttributes.matchesContainer(nextSibling)) {
540 nextSibling.textContent = str + nextSibling.textContent;
541 this.cursorNode_ = nextSibling;
542 this.cursorOffset_ = strLength;
543 return;
544 }
545
546 var newNode = this.textAttributes.createContainer(str);
547 this.cursorRowNode_.insertBefore(newNode, nextSibling);
548 this.cursorNode_ = newNode;
549 // We specifically need to include any missing whitespace here, since it's
550 // going in a new node.
551 this.cursorOffset_ = str.length;
552 return;
553 }
554
555 // Worst case, we're somewhere in the middle of the cursor node. We'll
556 // have to split it into two nodes and insert our new container in between.
557 this.splitNode_(cursorNode, offset);
558 var newNode = this.textAttributes.createContainer(str);
559 this.cursorRowNode_.insertBefore(newNode, cursorNode.nextSibling);
560 this.cursorNode_ = newNode;
561 this.cursorOffset_ = strLength;
562};
563
564/**
565 * Insert an array of DOM nodes at the beginning of the cursor row.
566 *
567 * This does not pay attention to the cursor column, it only prepends to the
568 * beginning of the current row.
569 *
570 * This method does not attempt to coalesce rows of the same style. It assumes
571 * that the rows being inserted have already been coalesced, and that there
572 * would be no gain in coalescing only the final node.
rginda35c456b2012-02-09 17:29:05 -0800573 *
574 * The cursor will be reset to the zero'th column.
rgindaa19afe22012-01-25 15:40:22 -0800575 */
576hterm.Screen.prototype.prependNodes = function(ary) {
577 var parentNode = this.cursorRowNode_;
578
579 for (var i = ary.length - 1; i >= 0; i--) {
580 parentNode.insertBefore(ary[i], parentNode.firstChild);
581 }
rginda35c456b2012-02-09 17:29:05 -0800582
583 // We have to leave the cursor in a sensible state so we don't confuse
584 // setCursorPosition. It's fastest to just leave it at the start of
585 // the row. If the caller wants it somewhere else, they can move it
586 // on their own.
587 this.cursorPosition.column = 0;
588 this.cursorNode_ = parentNode.firstChild;
589 this.cursorOffset_ = 0;
rginda8ba33642011-12-14 12:31:31 -0800590};
591
592/**
593 * Overwrite the text at the current cursor position.
594 *
rgindaa19afe22012-01-25 15:40:22 -0800595 * You must call maybeClipCurrentRow() after in order to check overflow.
rginda8ba33642011-12-14 12:31:31 -0800596 */
597hterm.Screen.prototype.overwriteString = function(str) {
598 var maxLength = this.columnCount_ - this.cursorPosition.column;
599 if (!maxLength)
rgindaa19afe22012-01-25 15:40:22 -0800600 return [str];
601
602 if ((this.cursorNode_.textContent.substr(this.cursorOffset_) == str) &&
603 this.textAttributes.matchesContainer(this.cursorNode_)) {
604 // This overwrite would be a no-op, just move the cursor and return.
605 this.cursorOffset_ += str.length;
606 this.cursorPosition.column += str.length;
607 return;
608 }
rginda8ba33642011-12-14 12:31:31 -0800609
610 this.deleteChars(Math.min(str.length, maxLength));
rgindaa19afe22012-01-25 15:40:22 -0800611 this.insertString(str);
rginda8ba33642011-12-14 12:31:31 -0800612};
613
614/**
615 * Forward-delete one or more characters at the current cursor position.
616 *
617 * Text to the right of the deleted characters is shifted left. Only affects
618 * characters on the same row as the cursor.
619 *
620 * @param {integer} count The number of characters to delete. This is clamped
621 * to the column width minus the cursor column.
622 */
623hterm.Screen.prototype.deleteChars = function(count) {
624 var node = this.cursorNode_;
625 var offset = this.cursorOffset_;
626
rgindaa19afe22012-01-25 15:40:22 -0800627 if (node.textContent.length <= offset && !node.nextSibling) {
628 // There's nothing after this node/offset to delete, buh bye.
629 return;
630 }
631
rginda8ba33642011-12-14 12:31:31 -0800632 while (node && count) {
633 var startLength = node.textContent.length;
634
635 node.textContent = node.textContent.substr(0, offset) +
636 node.textContent.substr(offset + count);
637
638 var endLength = node.textContent.length;
639 count -= startLength - endLength;
640
641 if (endLength == 0 && node != this.cursorNode_) {
642 var nextNode = node.nextSibling;
643 node.parentNode.removeChild(node);
644 node = nextNode;
645 } else {
646 node = node.nextSibling;
647 }
648
649 offset = 0;
650 }
651};