blob: 9aed1e53bb3d032a70023411c8bc2fec6b8a0e5d [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
rginda8ba33642011-12-14 12:31:31 -08007/**
8 * @fileoverview This class represents a single terminal screen full of text.
9 *
10 * It maintains the current cursor position and has basic methods for text
11 * insert and overwrite, and adding or removing rows from the screen.
12 *
13 * This class has no knowledge of the scrollback buffer.
14 *
15 * The number of rows on the screen is determined only by the number of rows
16 * that the caller inserts into the screen. If a caller wants to ensure a
17 * constant number of rows on the screen, it's their responsibility to remove a
18 * row for each row inserted.
19 *
20 * The screen width, in contrast, is enforced locally.
21 *
22 *
23 * In practice...
24 * - The hterm.Terminal class holds two hterm.Screen instances. One for the
25 * primary screen and one for the alternate screen.
26 *
27 * - The html.Screen class only cares that rows are HTMLElements. In the
28 * larger context of hterm, however, the rows happen to be displayed by an
29 * hterm.ScrollPort and have to follow a few rules as a result. Each
30 * row must be rooted by the custom HTML tag 'x-row', and each must have a
31 * rowIndex property that corresponds to the index of the row in the context
32 * of the scrollback buffer. These invariants are enforced by hterm.Terminal
33 * because that is the class using the hterm.Screen in the context of an
34 * hterm.ScrollPort.
35 */
36
37/**
38 * Create a new screen instance.
39 *
40 * The screen initially has no rows and a maximum column count of 0.
41 *
Mike Frysinger60a156d2019-06-13 10:15:45 -040042 * @param {integer=} columnCount The maximum number of columns for this
rginda8ba33642011-12-14 12:31:31 -080043 * screen. See insertString() and overwriteString() for information about
44 * what happens when too many characters are added too a row. Defaults to
45 * 0 if not provided.
46 */
Mike Frysinger60a156d2019-06-13 10:15:45 -040047hterm.Screen = function(columnCount=0) {
rginda8ba33642011-12-14 12:31:31 -080048 /**
49 * Public, read-only access to the rows in this screen.
50 */
51 this.rowsArray = [];
52
53 // The max column width for this screen.
Mike Frysinger60a156d2019-06-13 10:15:45 -040054 this.columnCount_ = columnCount;
rginda8ba33642011-12-14 12:31:31 -080055
rgindaa19afe22012-01-25 15:40:22 -080056 // The current color, bold, underline and blink attributes.
57 this.textAttributes = new hterm.TextAttributes(window.document);
58
rginda87b86462011-12-14 13:48:03 -080059 // Current zero-based cursor coordinates.
60 this.cursorPosition = new hterm.RowCol(0, 0);
rginda8ba33642011-12-14 12:31:31 -080061
Mike Frysingera2cacaa2017-11-29 13:51:09 -080062 // Saved state used by DECSC and related settings. This is only for saving
63 // and restoring specific state, not for the current/active state.
64 this.cursorState_ = new hterm.Screen.CursorState(this);
65
rginda8ba33642011-12-14 12:31:31 -080066 // The node containing the row that the cursor is positioned on.
67 this.cursorRowNode_ = null;
68
69 // The node containing the span of text that the cursor is positioned on.
70 this.cursorNode_ = null;
71
Ricky Liang48f05cb2013-12-31 23:35:29 +080072 // The offset in column width into cursorNode_ where the cursor is positioned.
rginda8ba33642011-12-14 12:31:31 -080073 this.cursorOffset_ = null;
Mike Frysinger664e9992017-05-19 01:24:24 -040074
75 // Regexes for expanding word selections.
76 this.wordBreakMatchLeft = null;
77 this.wordBreakMatchRight = null;
78 this.wordBreakMatchMiddle = null;
rginda8ba33642011-12-14 12:31:31 -080079};
80
81/**
82 * Return the screen size as an hterm.Size object.
83 *
84 * @return {hterm.Size} hterm.Size object representing the current number
85 * of rows and columns in this screen.
86 */
87hterm.Screen.prototype.getSize = function() {
88 return new hterm.Size(this.columnCount_, this.rowsArray.length);
89};
90
91/**
92 * Return the current number of rows in this screen.
93 *
94 * @return {integer} The number of rows in this screen.
95 */
96hterm.Screen.prototype.getHeight = function() {
97 return this.rowsArray.length;
98};
99
100/**
101 * Return the current number of columns in this screen.
102 *
103 * @return {integer} The number of columns in this screen.
104 */
105hterm.Screen.prototype.getWidth = function() {
106 return this.columnCount_;
107};
108
109/**
110 * Set the maximum number of columns per row.
111 *
rginda8ba33642011-12-14 12:31:31 -0800112 * @param {integer} count The maximum number of columns per row.
113 */
114hterm.Screen.prototype.setColumnCount = function(count) {
rginda2312fff2012-01-05 16:20:52 -0800115 this.columnCount_ = count;
116
rgindacbbd7482012-06-13 15:06:16 -0700117 if (this.cursorPosition.column >= count)
118 this.setCursorPosition(this.cursorPosition.row, count - 1);
rginda8ba33642011-12-14 12:31:31 -0800119};
120
121/**
122 * Remove the first row from the screen and return it.
123 *
124 * @return {HTMLElement} The first row in this screen.
125 */
126hterm.Screen.prototype.shiftRow = function() {
127 return this.shiftRows(1)[0];
rginda87b86462011-12-14 13:48:03 -0800128};
rginda8ba33642011-12-14 12:31:31 -0800129
130/**
131 * Remove rows from the top of the screen and return them as an array.
132 *
133 * @param {integer} count The number of rows to remove.
134 * @return {Array.<HTMLElement>} The selected rows.
135 */
136hterm.Screen.prototype.shiftRows = function(count) {
137 return this.rowsArray.splice(0, count);
138};
139
140/**
141 * Insert a row at the top of the screen.
142 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500143 * @param {HTMLElement} row The row to insert.
rginda8ba33642011-12-14 12:31:31 -0800144 */
145hterm.Screen.prototype.unshiftRow = function(row) {
146 this.rowsArray.splice(0, 0, row);
147};
148
149/**
150 * Insert rows at the top of the screen.
151 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500152 * @param {Array.<HTMLElement>} rows The rows to insert.
rginda8ba33642011-12-14 12:31:31 -0800153 */
154hterm.Screen.prototype.unshiftRows = function(rows) {
155 this.rowsArray.unshift.apply(this.rowsArray, rows);
156};
157
158/**
159 * Remove the last row from the screen and return it.
160 *
161 * @return {HTMLElement} The last row in this screen.
162 */
163hterm.Screen.prototype.popRow = function() {
164 return this.popRows(1)[0];
165};
166
167/**
168 * Remove rows from the bottom of the screen and return them as an array.
169 *
170 * @param {integer} count The number of rows to remove.
171 * @return {Array.<HTMLElement>} The selected rows.
172 */
173hterm.Screen.prototype.popRows = function(count) {
174 return this.rowsArray.splice(this.rowsArray.length - count, count);
175};
176
177/**
178 * Insert a row at the bottom of the screen.
179 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500180 * @param {HTMLElement} row The row to insert.
rginda8ba33642011-12-14 12:31:31 -0800181 */
182hterm.Screen.prototype.pushRow = function(row) {
183 this.rowsArray.push(row);
184};
185
186/**
187 * Insert rows at the bottom of the screen.
188 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500189 * @param {Array.<HTMLElement>} rows The rows to insert.
rginda8ba33642011-12-14 12:31:31 -0800190 */
191hterm.Screen.prototype.pushRows = function(rows) {
192 rows.push.apply(this.rowsArray, rows);
193};
194
195/**
Evan Jones2600d4f2016-12-06 09:29:36 -0500196 * Insert a row at the specified row of the screen.
rginda8ba33642011-12-14 12:31:31 -0800197 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500198 * @param {integer} index The index to insert the row.
199 * @param {HTMLElement} row The row to insert.
rginda8ba33642011-12-14 12:31:31 -0800200 */
201hterm.Screen.prototype.insertRow = function(index, row) {
202 this.rowsArray.splice(index, 0, row);
203};
204
205/**
Evan Jones2600d4f2016-12-06 09:29:36 -0500206 * Insert rows at the specified row of the screen.
rginda8ba33642011-12-14 12:31:31 -0800207 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500208 * @param {integer} index The index to insert the rows.
209 * @param {Array.<HTMLElement>} rows The rows to insert.
rginda8ba33642011-12-14 12:31:31 -0800210 */
211hterm.Screen.prototype.insertRows = function(index, rows) {
212 for (var i = 0; i < rows.length; i++) {
213 this.rowsArray.splice(index + i, 0, rows[i]);
214 }
215};
216
217/**
Evan Jones2600d4f2016-12-06 09:29:36 -0500218 * Remove a row from the screen and return it.
rginda8ba33642011-12-14 12:31:31 -0800219 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500220 * @param {integer} index The index of the row to remove.
rginda8ba33642011-12-14 12:31:31 -0800221 * @return {HTMLElement} The selected row.
222 */
223hterm.Screen.prototype.removeRow = function(index) {
224 return this.rowsArray.splice(index, 1)[0];
225};
226
227/**
228 * Remove rows from the bottom of the screen and return them as an array.
229 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500230 * @param {integer} index The index to start removing rows.
rginda8ba33642011-12-14 12:31:31 -0800231 * @param {integer} count The number of rows to remove.
232 * @return {Array.<HTMLElement>} The selected rows.
233 */
234hterm.Screen.prototype.removeRows = function(index, count) {
235 return this.rowsArray.splice(index, count);
236};
237
238/**
239 * Invalidate the current cursor position.
240 *
rginda87b86462011-12-14 13:48:03 -0800241 * This sets this.cursorPosition to (0, 0) and clears out some internal
rginda8ba33642011-12-14 12:31:31 -0800242 * data.
243 *
244 * Attempting to insert or overwrite text while the cursor position is invalid
245 * will raise an obscure exception.
246 */
247hterm.Screen.prototype.invalidateCursorPosition = function() {
rginda87b86462011-12-14 13:48:03 -0800248 this.cursorPosition.move(0, 0);
rginda8ba33642011-12-14 12:31:31 -0800249 this.cursorRowNode_ = null;
250 this.cursorNode_ = null;
251 this.cursorOffset_ = null;
252};
253
254/**
rginda8ba33642011-12-14 12:31:31 -0800255 * Clear the contents of the cursor row.
rginda8ba33642011-12-14 12:31:31 -0800256 */
257hterm.Screen.prototype.clearCursorRow = function() {
258 this.cursorRowNode_.innerHTML = '';
rgindaa09e7332012-08-17 12:49:51 -0700259 this.cursorRowNode_.removeAttribute('line-overflow');
rginda8ba33642011-12-14 12:31:31 -0800260 this.cursorOffset_ = 0;
rginda8ba33642011-12-14 12:31:31 -0800261 this.cursorPosition.column = 0;
rginda2312fff2012-01-05 16:20:52 -0800262 this.cursorPosition.overflow = false;
Robert Ginda7fd57082012-09-25 14:41:47 -0700263
264 var text;
265 if (this.textAttributes.isDefault()) {
266 text = '';
267 } else {
Mike Frysinger73e56462019-07-17 00:23:46 -0500268 text = ' '.repeat(this.columnCount_);
Robert Ginda7fd57082012-09-25 14:41:47 -0700269 }
270
Zhu Qunying30d40712017-03-14 16:27:00 -0700271 // We shouldn't honor inverse colors when clearing an area, to match
272 // xterm's back color erase behavior.
Edoardo Spadolini2fd43642014-08-23 22:59:57 +0200273 var inverse = this.textAttributes.inverse;
274 this.textAttributes.inverse = false;
275 this.textAttributes.syncColors();
276
Robert Ginda7fd57082012-09-25 14:41:47 -0700277 var node = this.textAttributes.createContainer(text);
278 this.cursorRowNode_.appendChild(node);
279 this.cursorNode_ = node;
Edoardo Spadolini2fd43642014-08-23 22:59:57 +0200280
281 this.textAttributes.inverse = inverse;
282 this.textAttributes.syncColors();
rginda8ba33642011-12-14 12:31:31 -0800283};
284
285/**
rgindaa09e7332012-08-17 12:49:51 -0700286 * Mark the current row as having overflowed to the next line.
287 *
288 * The line overflow state is used when converting a range of rows into text.
289 * It makes it possible to recombine two or more overflow terminal rows into
290 * a single line.
291 *
292 * This is distinct from the cursor being in the overflow state. Cursor
293 * overflow indicates that printing at the cursor position will commit a
294 * line overflow, unless it is preceded by a repositioning of the cursor
295 * to a non-overflow state.
296 */
297hterm.Screen.prototype.commitLineOverflow = function() {
298 this.cursorRowNode_.setAttribute('line-overflow', true);
299};
300
301/**
rginda8ba33642011-12-14 12:31:31 -0800302 * Relocate the cursor to a give row and column.
303 *
304 * @param {integer} row The zero based row.
305 * @param {integer} column The zero based column.
306 */
307hterm.Screen.prototype.setCursorPosition = function(row, column) {
rginda11057d52012-04-25 12:29:56 -0700308 if (!this.rowsArray.length) {
309 console.warn('Attempt to set cursor position on empty screen.');
310 return;
311 }
312
rginda87b86462011-12-14 13:48:03 -0800313 if (row >= this.rowsArray.length) {
rgindacbbd7482012-06-13 15:06:16 -0700314 console.error('Row out of bounds: ' + row);
rginda87b86462011-12-14 13:48:03 -0800315 row = this.rowsArray.length - 1;
316 } else if (row < 0) {
rgindacbbd7482012-06-13 15:06:16 -0700317 console.error('Row out of bounds: ' + row);
rginda87b86462011-12-14 13:48:03 -0800318 row = 0;
319 }
320
321 if (column >= this.columnCount_) {
rgindacbbd7482012-06-13 15:06:16 -0700322 console.error('Column out of bounds: ' + column);
rginda87b86462011-12-14 13:48:03 -0800323 column = this.columnCount_ - 1;
324 } else if (column < 0) {
rgindacbbd7482012-06-13 15:06:16 -0700325 console.error('Column out of bounds: ' + column);
rginda87b86462011-12-14 13:48:03 -0800326 column = 0;
327 }
rginda8ba33642011-12-14 12:31:31 -0800328
rginda2312fff2012-01-05 16:20:52 -0800329 this.cursorPosition.overflow = false;
330
rginda8ba33642011-12-14 12:31:31 -0800331 var rowNode = this.rowsArray[row];
332 var node = rowNode.firstChild;
333
334 if (!node) {
335 node = rowNode.ownerDocument.createTextNode('');
336 rowNode.appendChild(node);
337 }
338
rgindaa19afe22012-01-25 15:40:22 -0800339 var currentColumn = 0;
340
rginda8ba33642011-12-14 12:31:31 -0800341 if (rowNode == this.cursorRowNode_) {
342 if (column >= this.cursorPosition.column - this.cursorOffset_) {
343 node = this.cursorNode_;
344 currentColumn = this.cursorPosition.column - this.cursorOffset_;
345 }
346 } else {
347 this.cursorRowNode_ = rowNode;
348 }
349
350 this.cursorPosition.move(row, column);
351
352 while (node) {
353 var offset = column - currentColumn;
Ricky Liang48f05cb2013-12-31 23:35:29 +0800354 var width = hterm.TextAttributes.nodeWidth(node);
355 if (!node.nextSibling || width > offset) {
rginda8ba33642011-12-14 12:31:31 -0800356 this.cursorNode_ = node;
357 this.cursorOffset_ = offset;
358 return;
359 }
360
Ricky Liang48f05cb2013-12-31 23:35:29 +0800361 currentColumn += width;
rginda8ba33642011-12-14 12:31:31 -0800362 node = node.nextSibling;
363 }
364};
365
366/**
rginda87b86462011-12-14 13:48:03 -0800367 * Set the provided selection object to be a caret selection at the current
368 * cursor position.
369 */
370hterm.Screen.prototype.syncSelectionCaret = function(selection) {
Rob Spies06533ba2014-04-24 11:20:37 -0700371 try {
372 selection.collapse(this.cursorNode_, this.cursorOffset_);
373 } catch (firefoxIgnoredException) {
374 // FF can throw an exception if the range is off, rather than just not
375 // performing the collapse.
376 }
rginda87b86462011-12-14 13:48:03 -0800377};
378
379/**
rgindaa19afe22012-01-25 15:40:22 -0800380 * Split a single node into two nodes at the given offset.
rginda8ba33642011-12-14 12:31:31 -0800381 *
rgindaa19afe22012-01-25 15:40:22 -0800382 * For example:
383 * Given the DOM fragment '<div><span>Hello World</span></div>', call splitNode_
Zhu Qunying30d40712017-03-14 16:27:00 -0700384 * passing the span and an offset of 6. This would modify the fragment to
rgindaa19afe22012-01-25 15:40:22 -0800385 * become: '<div><span>Hello </span><span>World</span></div>'. If the span
386 * had any attributes they would have been copied to the new span as well.
387 *
388 * The to-be-split node must have a container, so that the new node can be
389 * placed next to it.
390 *
391 * @param {HTMLNode} node The node to split.
392 * @param {integer} offset The offset into the node where the split should
393 * occur.
rginda8ba33642011-12-14 12:31:31 -0800394 */
rgindaa19afe22012-01-25 15:40:22 -0800395hterm.Screen.prototype.splitNode_ = function(node, offset) {
rginda35c456b2012-02-09 17:29:05 -0800396 var afterNode = node.cloneNode(false);
rgindaa19afe22012-01-25 15:40:22 -0800397
rginda35c456b2012-02-09 17:29:05 -0800398 var textContent = node.textContent;
Ricky Liang48f05cb2013-12-31 23:35:29 +0800399 node.textContent = hterm.TextAttributes.nodeSubstr(node, 0, offset);
400 afterNode.textContent = lib.wc.substr(textContent, offset);
rgindaa19afe22012-01-25 15:40:22 -0800401
Ricky Liang48f05cb2013-12-31 23:35:29 +0800402 if (afterNode.textContent)
403 node.parentNode.insertBefore(afterNode, node.nextSibling);
404 if (!node.textContent)
405 node.parentNode.removeChild(node);
rginda8ba33642011-12-14 12:31:31 -0800406};
407
408/**
rgindaa9abdd82012-08-06 18:05:09 -0700409 * Ensure that text is clipped and the cursor is clamped to the column count.
rgindaa19afe22012-01-25 15:40:22 -0800410 */
rgindaa9abdd82012-08-06 18:05:09 -0700411hterm.Screen.prototype.maybeClipCurrentRow = function() {
Ricky Liang48f05cb2013-12-31 23:35:29 +0800412 var width = hterm.TextAttributes.nodeWidth(this.cursorRowNode_);
413
414 if (width <= this.columnCount_) {
rgindaa9abdd82012-08-06 18:05:09 -0700415 // Current row does not need clipping, but may need clamping.
416 if (this.cursorPosition.column >= this.columnCount_) {
417 this.setCursorPosition(this.cursorPosition.row, this.columnCount_ - 1);
418 this.cursorPosition.overflow = true;
419 }
rgindaa19afe22012-01-25 15:40:22 -0800420
rgindaa9abdd82012-08-06 18:05:09 -0700421 return;
422 }
423
424 // Save off the current column so we can maybe restore it later.
425 var currentColumn = this.cursorPosition.column;
426
427 // Move the cursor to the final column.
428 this.setCursorPosition(this.cursorPosition.row, this.columnCount_ - 1);
429
430 // Remove any text that partially overflows.
Ricky Liang48f05cb2013-12-31 23:35:29 +0800431 width = hterm.TextAttributes.nodeWidth(this.cursorNode_);
432
433 if (this.cursorOffset_ < width - 1) {
434 this.cursorNode_.textContent = hterm.TextAttributes.nodeSubstr(
435 this.cursorNode_, 0, this.cursorOffset_ + 1);
rgindaa9abdd82012-08-06 18:05:09 -0700436 }
437
438 // Remove all nodes after the cursor.
rgindaa19afe22012-01-25 15:40:22 -0800439 var rowNode = this.cursorRowNode_;
440 var node = this.cursorNode_.nextSibling;
441
442 while (node) {
rgindaa19afe22012-01-25 15:40:22 -0800443 rowNode.removeChild(node);
444 node = this.cursorNode_.nextSibling;
445 }
446
Robert Ginda7fd57082012-09-25 14:41:47 -0700447 if (currentColumn < this.columnCount_) {
rgindaa9abdd82012-08-06 18:05:09 -0700448 // If the cursor was within the screen before we started then restore its
449 // position.
rgindaa19afe22012-01-25 15:40:22 -0800450 this.setCursorPosition(this.cursorPosition.row, currentColumn);
rgindaa9abdd82012-08-06 18:05:09 -0700451 } else {
452 // Otherwise leave it at the the last column in the overflow state.
453 this.cursorPosition.overflow = true;
rgindaa19afe22012-01-25 15:40:22 -0800454 }
rgindaa19afe22012-01-25 15:40:22 -0800455};
456
457/**
458 * Insert a string at the current character position using the current
459 * text attributes.
460 *
rgindaa09e7332012-08-17 12:49:51 -0700461 * You must call maybeClipCurrentRow() after in order to clip overflowed
462 * text and clamp the cursor.
463 *
464 * It is also up to the caller to properly maintain the line overflow state
465 * using hterm.Screen..commitLineOverflow().
rginda8ba33642011-12-14 12:31:31 -0800466 */
Mike Frysinger6380bed2017-08-24 18:46:39 -0400467hterm.Screen.prototype.insertString = function(str, wcwidth=undefined) {
rgindaa19afe22012-01-25 15:40:22 -0800468 var cursorNode = this.cursorNode_;
469 var cursorNodeText = cursorNode.textContent;
rginda8ba33642011-12-14 12:31:31 -0800470
Robert Gindaa21dfb32013-10-31 14:17:45 -0700471 this.cursorRowNode_.removeAttribute('line-overflow');
472
Ricky Liang48f05cb2013-12-31 23:35:29 +0800473 // We may alter the width of the string by prepending some missing
474 // whitespaces, so we need to record the string width ahead of time.
Mike Frysinger6380bed2017-08-24 18:46:39 -0400475 if (wcwidth === undefined)
476 wcwidth = lib.wc.strWidth(str);
rginda8ba33642011-12-14 12:31:31 -0800477
rgindaa19afe22012-01-25 15:40:22 -0800478 // No matter what, before this function exits the cursor column will have
479 // moved this much.
Mike Frysinger6380bed2017-08-24 18:46:39 -0400480 this.cursorPosition.column += wcwidth;
rginda8ba33642011-12-14 12:31:31 -0800481
rgindaa19afe22012-01-25 15:40:22 -0800482 // Local cache of the cursor offset.
483 var offset = this.cursorOffset_;
rginda8ba33642011-12-14 12:31:31 -0800484
rgindaa19afe22012-01-25 15:40:22 -0800485 // Reverse offset is the offset measured from the end of the string.
486 // Zero implies that the cursor is at the end of the cursor node.
Ricky Liang48f05cb2013-12-31 23:35:29 +0800487 var reverseOffset = hterm.TextAttributes.nodeWidth(cursorNode) - offset;
rgindaa19afe22012-01-25 15:40:22 -0800488
489 if (reverseOffset < 0) {
490 // A negative reverse offset means the cursor is positioned past the end
491 // of the characters on this line. We'll need to insert the missing
492 // whitespace.
Mike Frysinger73e56462019-07-17 00:23:46 -0500493 const ws = ' '.repeat(-reverseOffset);
rgindaa19afe22012-01-25 15:40:22 -0800494
Brad Town7de83302015-03-12 02:10:32 -0700495 // This whitespace should be completely unstyled. Underline, background
496 // color, and strikethrough would be visible on whitespace, so we can't use
497 // one of those spans to hold the text.
Edoardo Spadolini198828a2014-08-08 00:22:51 +0200498 if (!(this.textAttributes.underline ||
Brad Town7de83302015-03-12 02:10:32 -0700499 this.textAttributes.strikethrough ||
Edoardo Spadolini198828a2014-08-08 00:22:51 +0200500 this.textAttributes.background ||
501 this.textAttributes.wcNode ||
Mike Frysinger1e98c0f2017-08-15 01:21:31 -0400502 !this.textAttributes.asciiNode ||
Edoardo Spadolini198828a2014-08-08 00:22:51 +0200503 this.textAttributes.tileData != null)) {
rgindaa19afe22012-01-25 15:40:22 -0800504 // Best case scenario, we can just pretend the spaces were part of the
505 // original string.
506 str = ws + str;
Mike Frysinger6a4f2412017-08-31 01:11:25 -0400507 } else if (cursorNode.nodeType == Node.TEXT_NODE ||
Ricky Liang48f05cb2013-12-31 23:35:29 +0800508 !(cursorNode.wcNode ||
Mike Frysinger1e98c0f2017-08-15 01:21:31 -0400509 !cursorNode.asciiNode ||
Edoardo Spadolini198828a2014-08-08 00:22:51 +0200510 cursorNode.tileNode ||
Ricky Liang48f05cb2013-12-31 23:35:29 +0800511 cursorNode.style.textDecoration ||
Mike Frysinger09c54f42017-12-15 01:12:30 -0500512 cursorNode.style.textDecorationStyle ||
513 cursorNode.style.textDecorationLine ||
rgindaa19afe22012-01-25 15:40:22 -0800514 cursorNode.style.backgroundColor)) {
515 // Second best case, the current node is able to hold the whitespace.
516 cursorNode.textContent = (cursorNodeText += ws);
517 } else {
518 // Worst case, we have to create a new node to hold the whitespace.
519 var wsNode = cursorNode.ownerDocument.createTextNode(ws);
520 this.cursorRowNode_.insertBefore(wsNode, cursorNode.nextSibling);
521 this.cursorNode_ = cursorNode = wsNode;
522 this.cursorOffset_ = offset = -reverseOffset;
523 cursorNodeText = ws;
524 }
525
526 // We now know for sure that we're at the last character of the cursor node.
527 reverseOffset = 0;
rginda8ba33642011-12-14 12:31:31 -0800528 }
529
rgindaa19afe22012-01-25 15:40:22 -0800530 if (this.textAttributes.matchesContainer(cursorNode)) {
531 // The new text can be placed directly in the cursor node.
532 if (reverseOffset == 0) {
533 cursorNode.textContent = cursorNodeText + str;
534 } else if (offset == 0) {
535 cursorNode.textContent = str + cursorNodeText;
536 } else {
Ricky Liang48f05cb2013-12-31 23:35:29 +0800537 cursorNode.textContent =
538 hterm.TextAttributes.nodeSubstr(cursorNode, 0, offset) +
539 str + hterm.TextAttributes.nodeSubstr(cursorNode, offset);
rgindaa19afe22012-01-25 15:40:22 -0800540 }
rginda8ba33642011-12-14 12:31:31 -0800541
Mike Frysinger6380bed2017-08-24 18:46:39 -0400542 this.cursorOffset_ += wcwidth;
rgindaa19afe22012-01-25 15:40:22 -0800543 return;
rginda87b86462011-12-14 13:48:03 -0800544 }
545
rgindaa19afe22012-01-25 15:40:22 -0800546 // The cursor node is the wrong style for the new text. If we're at the
547 // beginning or end of the cursor node, then the adjacent node is also a
548 // potential candidate.
rginda8ba33642011-12-14 12:31:31 -0800549
rgindaa19afe22012-01-25 15:40:22 -0800550 if (offset == 0) {
551 // At the beginning of the cursor node, the check the previous sibling.
552 var previousSibling = cursorNode.previousSibling;
553 if (previousSibling &&
554 this.textAttributes.matchesContainer(previousSibling)) {
555 previousSibling.textContent += str;
556 this.cursorNode_ = previousSibling;
Ricky Liang48f05cb2013-12-31 23:35:29 +0800557 this.cursorOffset_ = lib.wc.strWidth(previousSibling.textContent);
rgindaa19afe22012-01-25 15:40:22 -0800558 return;
559 }
560
561 var newNode = this.textAttributes.createContainer(str);
562 this.cursorRowNode_.insertBefore(newNode, cursorNode);
563 this.cursorNode_ = newNode;
Mike Frysinger6380bed2017-08-24 18:46:39 -0400564 this.cursorOffset_ = wcwidth;
rgindaa19afe22012-01-25 15:40:22 -0800565 return;
566 }
567
568 if (reverseOffset == 0) {
569 // At the end of the cursor node, the check the next sibling.
570 var nextSibling = cursorNode.nextSibling;
571 if (nextSibling &&
572 this.textAttributes.matchesContainer(nextSibling)) {
573 nextSibling.textContent = str + nextSibling.textContent;
574 this.cursorNode_ = nextSibling;
Ricky Liang48f05cb2013-12-31 23:35:29 +0800575 this.cursorOffset_ = lib.wc.strWidth(str);
rgindaa19afe22012-01-25 15:40:22 -0800576 return;
577 }
578
579 var newNode = this.textAttributes.createContainer(str);
580 this.cursorRowNode_.insertBefore(newNode, nextSibling);
581 this.cursorNode_ = newNode;
582 // We specifically need to include any missing whitespace here, since it's
583 // going in a new node.
Ricky Liang48f05cb2013-12-31 23:35:29 +0800584 this.cursorOffset_ = hterm.TextAttributes.nodeWidth(newNode);
rgindaa19afe22012-01-25 15:40:22 -0800585 return;
586 }
587
588 // Worst case, we're somewhere in the middle of the cursor node. We'll
589 // have to split it into two nodes and insert our new container in between.
590 this.splitNode_(cursorNode, offset);
591 var newNode = this.textAttributes.createContainer(str);
592 this.cursorRowNode_.insertBefore(newNode, cursorNode.nextSibling);
593 this.cursorNode_ = newNode;
Mike Frysinger6380bed2017-08-24 18:46:39 -0400594 this.cursorOffset_ = wcwidth;
rgindaa19afe22012-01-25 15:40:22 -0800595};
596
597/**
rginda8ba33642011-12-14 12:31:31 -0800598 * Overwrite the text at the current cursor position.
599 *
rgindaa09e7332012-08-17 12:49:51 -0700600 * You must call maybeClipCurrentRow() after in order to clip overflowed
601 * text and clamp the cursor.
602 *
603 * It is also up to the caller to properly maintain the line overflow state
604 * using hterm.Screen..commitLineOverflow().
rginda8ba33642011-12-14 12:31:31 -0800605 */
Mike Frysinger6380bed2017-08-24 18:46:39 -0400606hterm.Screen.prototype.overwriteString = function(str, wcwidth=undefined) {
rginda8ba33642011-12-14 12:31:31 -0800607 var maxLength = this.columnCount_ - this.cursorPosition.column;
608 if (!maxLength)
Mike Frysinger159b7392019-03-26 11:08:32 -0700609 return;
rgindaa19afe22012-01-25 15:40:22 -0800610
Mike Frysinger6380bed2017-08-24 18:46:39 -0400611 if (wcwidth === undefined)
612 wcwidth = lib.wc.strWidth(str);
613
Ricky Liang48f05cb2013-12-31 23:35:29 +0800614 if (this.textAttributes.matchesContainer(this.cursorNode_) &&
615 this.cursorNode_.textContent.substr(this.cursorOffset_) == str) {
rgindaa19afe22012-01-25 15:40:22 -0800616 // This overwrite would be a no-op, just move the cursor and return.
Mike Frysinger6380bed2017-08-24 18:46:39 -0400617 this.cursorOffset_ += wcwidth;
618 this.cursorPosition.column += wcwidth;
rgindaa19afe22012-01-25 15:40:22 -0800619 return;
620 }
rginda8ba33642011-12-14 12:31:31 -0800621
Mike Frysinger6380bed2017-08-24 18:46:39 -0400622 this.deleteChars(Math.min(wcwidth, maxLength));
623 this.insertString(str, wcwidth);
rginda8ba33642011-12-14 12:31:31 -0800624};
625
626/**
627 * Forward-delete one or more characters at the current cursor position.
628 *
629 * Text to the right of the deleted characters is shifted left. Only affects
630 * characters on the same row as the cursor.
631 *
Ricky Liang48f05cb2013-12-31 23:35:29 +0800632 * @param {integer} count The column width of characters to delete. This is
633 * clamped to the column width minus the cursor column.
634 * @return {integer} The column width of the characters actually deleted.
rginda8ba33642011-12-14 12:31:31 -0800635 */
636hterm.Screen.prototype.deleteChars = function(count) {
637 var node = this.cursorNode_;
638 var offset = this.cursorOffset_;
639
Robert Ginda7fd57082012-09-25 14:41:47 -0700640 var currentCursorColumn = this.cursorPosition.column;
641 count = Math.min(count, this.columnCount_ - currentCursorColumn);
642 if (!count)
643 return 0;
644
645 var rv = count;
Ricky Liang48f05cb2013-12-31 23:35:29 +0800646 var startLength, endLength;
rgindaa19afe22012-01-25 15:40:22 -0800647
rginda8ba33642011-12-14 12:31:31 -0800648 while (node && count) {
Mike Frysinger859bcbd2017-08-28 23:48:43 -0400649 // Sanity check so we don't loop forever, but we don't also go quietly.
650 if (count < 0) {
651 console.error(`Deleting ${rv} chars went negative: ${count}`);
652 break;
653 }
654
Ricky Liang48f05cb2013-12-31 23:35:29 +0800655 startLength = hterm.TextAttributes.nodeWidth(node);
656 node.textContent = hterm.TextAttributes.nodeSubstr(node, 0, offset) +
657 hterm.TextAttributes.nodeSubstr(node, offset + count);
658 endLength = hterm.TextAttributes.nodeWidth(node);
Mike Frysinger859bcbd2017-08-28 23:48:43 -0400659
660 // Deal with splitting wide characters. There are two ways: we could delete
661 // the first column or the second column. In both cases, we delete the wide
662 // character and replace one of the columns with a space (since the other
663 // was deleted). If there are more chars to delete, the next loop will pick
664 // up the slack.
665 if (node.wcNode && offset < startLength &&
666 ((endLength && startLength == endLength) || (!endLength && offset == 1))) {
Ricky Liang48f05cb2013-12-31 23:35:29 +0800667 // No characters were deleted when there should be. We're probably trying
668 // to delete one column width from a wide character node. We remove the
669 // wide character node here and replace it with a single space.
670 var spaceNode = this.textAttributes.createContainer(' ');
Mike Frysinger859bcbd2017-08-28 23:48:43 -0400671 node.parentNode.insertBefore(spaceNode, offset ? node : node.nextSibling);
Ricky Liang48f05cb2013-12-31 23:35:29 +0800672 node.textContent = '';
673 endLength = 0;
674 count -= 1;
Mike Frysinger859bcbd2017-08-28 23:48:43 -0400675 } else
676 count -= startLength - endLength;
rginda8ba33642011-12-14 12:31:31 -0800677
Ricky Liang48f05cb2013-12-31 23:35:29 +0800678 var nextNode = node.nextSibling;
679 if (endLength == 0 && node != this.cursorNode_) {
680 node.parentNode.removeChild(node);
681 }
682 node = nextNode;
rginda8ba33642011-12-14 12:31:31 -0800683 offset = 0;
684 }
Robert Ginda7fd57082012-09-25 14:41:47 -0700685
Ricky Liang48f05cb2013-12-31 23:35:29 +0800686 // Remove this.cursorNode_ if it is an empty non-text node.
Mike Frysinger6a4f2412017-08-31 01:11:25 -0400687 if (this.cursorNode_.nodeType != Node.TEXT_NODE &&
688 !this.cursorNode_.textContent) {
Ricky Liang48f05cb2013-12-31 23:35:29 +0800689 var cursorNode = this.cursorNode_;
690 if (cursorNode.previousSibling) {
691 this.cursorNode_ = cursorNode.previousSibling;
692 this.cursorOffset_ = hterm.TextAttributes.nodeWidth(
693 cursorNode.previousSibling);
694 } else if (cursorNode.nextSibling) {
695 this.cursorNode_ = cursorNode.nextSibling;
696 this.cursorOffset_ = 0;
697 } else {
698 var emptyNode = this.cursorRowNode_.ownerDocument.createTextNode('');
699 this.cursorRowNode_.appendChild(emptyNode);
700 this.cursorNode_ = emptyNode;
701 this.cursorOffset_ = 0;
702 }
703 this.cursorRowNode_.removeChild(cursorNode);
704 }
705
Robert Ginda7fd57082012-09-25 14:41:47 -0700706 return rv;
rginda8ba33642011-12-14 12:31:31 -0800707};
John Macinnesfb683832013-07-22 14:46:30 -0400708
709/**
710 * Finds first X-ROW of a line containing specified X-ROW.
711 * Used to support line overflow.
712 *
713 * @param {Node} row X-ROW to begin search for first row of line.
714 * @return {Node} The X-ROW that is at the beginning of the line.
715 **/
716hterm.Screen.prototype.getLineStartRow_ = function(row) {
717 while (row.previousSibling &&
718 row.previousSibling.hasAttribute('line-overflow')) {
719 row = row.previousSibling;
720 }
721 return row;
722};
723
724/**
725 * Gets text of a line beginning with row.
726 * Supports line overflow.
727 *
728 * @param {Node} row First X-ROW of line.
729 * @return {string} Text content of line.
730 **/
731hterm.Screen.prototype.getLineText_ = function(row) {
732 var rowText = "";
733 while (row) {
734 rowText += row.textContent;
735 if (row.hasAttribute('line-overflow')) {
736 row = row.nextSibling;
737 } else {
738 break;
739 }
740 }
741 return rowText;
742};
743
744/**
745 * Returns X-ROW that is ancestor of the node.
746 *
747 * @param {Node} node Node to get X-ROW ancestor for.
748 * @return {Node} X-ROW ancestor of node, or null if not found.
749 **/
750hterm.Screen.prototype.getXRowAncestor_ = function(node) {
751 while (node) {
752 if (node.nodeName === 'X-ROW')
753 break;
754 node = node.parentNode;
755 }
756 return node;
757};
758
759/**
760 * Returns position within line of character at offset within node.
761 * Supports line overflow.
762 *
763 * @param {Node} row X-ROW at beginning of line.
764 * @param {Node} node Node to get position of.
765 * @param {integer} offset Offset into node.
766 *
767 * @return {integer} Position within line of character at offset within node.
768 **/
769hterm.Screen.prototype.getPositionWithOverflow_ = function(row, node, offset) {
770 if (!node)
771 return -1;
772 var ancestorRow = this.getXRowAncestor_(node);
773 if (!ancestorRow)
774 return -1;
775 var position = 0;
776 while (ancestorRow != row) {
Ricky Liang48f05cb2013-12-31 23:35:29 +0800777 position += hterm.TextAttributes.nodeWidth(row);
John Macinnesfb683832013-07-22 14:46:30 -0400778 if (row.hasAttribute('line-overflow') && row.nextSibling) {
779 row = row.nextSibling;
780 } else {
781 return -1;
782 }
783 }
784 return position + this.getPositionWithinRow_(row, node, offset);
785};
786
787/**
788 * Returns position within row of character at offset within node.
789 * Does not support line overflow.
790 *
791 * @param {Node} row X-ROW to get position within.
792 * @param {Node} node Node to get position for.
793 * @param {integer} offset Offset within node to get position for.
794 * @return {integer} Position within row of character at offset within node.
795 **/
796hterm.Screen.prototype.getPositionWithinRow_ = function(row, node, offset) {
797 if (node.parentNode != row) {
Mike Frysinger498192d2017-06-26 18:23:31 -0400798 // If we traversed to the top node, then there's nothing to find here.
799 if (node.parentNode == null)
800 return -1;
801
John Macinnesfb683832013-07-22 14:46:30 -0400802 return this.getPositionWithinRow_(node.parentNode, node, offset) +
803 this.getPositionWithinRow_(row, node.parentNode, 0);
804 }
805 var position = 0;
806 for (var i = 0; i < row.childNodes.length; i++) {
807 var currentNode = row.childNodes[i];
808 if (currentNode == node)
809 return position + offset;
Ricky Liang48f05cb2013-12-31 23:35:29 +0800810 position += hterm.TextAttributes.nodeWidth(currentNode);
John Macinnesfb683832013-07-22 14:46:30 -0400811 }
812 return -1;
813};
814
815/**
816 * Returns the node and offset corresponding to position within line.
817 * Supports line overflow.
818 *
819 * @param {Node} row X-ROW at beginning of line.
820 * @param {integer} position Position within line to retrieve node and offset.
821 * @return {Array} Two element array containing node and offset respectively.
822 **/
823hterm.Screen.prototype.getNodeAndOffsetWithOverflow_ = function(row, position) {
Ricky Liang48f05cb2013-12-31 23:35:29 +0800824 while (row && position > hterm.TextAttributes.nodeWidth(row)) {
John Macinnesfb683832013-07-22 14:46:30 -0400825 if (row.hasAttribute('line-overflow') && row.nextSibling) {
Ricky Liang48f05cb2013-12-31 23:35:29 +0800826 position -= hterm.TextAttributes.nodeWidth(row);
John Macinnesfb683832013-07-22 14:46:30 -0400827 row = row.nextSibling;
828 } else {
829 return -1;
830 }
831 }
832 return this.getNodeAndOffsetWithinRow_(row, position);
833};
834
835/**
836 * Returns the node and offset corresponding to position within row.
837 * Does not support line overflow.
838 *
839 * @param {Node} row X-ROW to get position within.
840 * @param {integer} position Position within row to retrieve node and offset.
841 * @return {Array} Two element array containing node and offset respectively.
842 **/
843hterm.Screen.prototype.getNodeAndOffsetWithinRow_ = function(row, position) {
844 for (var i = 0; i < row.childNodes.length; i++) {
845 var node = row.childNodes[i];
Ricky Liang48f05cb2013-12-31 23:35:29 +0800846 var nodeTextWidth = hterm.TextAttributes.nodeWidth(node);
847 if (position <= nodeTextWidth) {
John Macinnesfb683832013-07-22 14:46:30 -0400848 if (node.nodeName === 'SPAN') {
849 /** Drill down to node contained by SPAN. **/
850 return this.getNodeAndOffsetWithinRow_(node, position);
851 } else {
852 return [node, position];
853 }
854 }
Ricky Liang48f05cb2013-12-31 23:35:29 +0800855 position -= nodeTextWidth;
John Macinnesfb683832013-07-22 14:46:30 -0400856 }
857 return null;
858};
859
860/**
861 * Returns the node and offset corresponding to position within line.
862 * Supports line overflow.
863 *
864 * @param {Node} row X-ROW at beginning of line.
865 * @param {integer} start Start position of range within line.
866 * @param {integer} end End position of range within line.
867 * @param {Range} range Range to modify.
868 **/
869hterm.Screen.prototype.setRange_ = function(row, start, end, range) {
870 var startNodeAndOffset = this.getNodeAndOffsetWithOverflow_(row, start);
871 if (startNodeAndOffset == null)
872 return;
873 var endNodeAndOffset = this.getNodeAndOffsetWithOverflow_(row, end);
874 if (endNodeAndOffset == null)
875 return;
876 range.setStart(startNodeAndOffset[0], startNodeAndOffset[1]);
877 range.setEnd(endNodeAndOffset[0], endNodeAndOffset[1]);
878};
879
880/**
John Lincae9b732018-03-08 13:56:35 +0800881 * Expands selection to surrounding string with word break matches.
John Macinnesfb683832013-07-22 14:46:30 -0400882 *
John Macinnesfb683832013-07-22 14:46:30 -0400883 * @param {Selection} selection Selection to expand.
John Lincae9b732018-03-08 13:56:35 +0800884 * @param {string} leftMatch left word break match.
885 * @param {string} rightMatch right word break match.
886 * @param {string} insideMatch inside word break match.
887 */
888hterm.Screen.prototype.expandSelectionWithWordBreakMatches_ =
889 function(selection, leftMatch, rightMatch, insideMatch) {
John Macinnesfb683832013-07-22 14:46:30 -0400890 if (!selection)
891 return;
892
893 var range = selection.getRangeAt(0);
894 if (!range || range.toString().match(/\s/))
895 return;
896
Raymes Khoury334625a2018-06-25 10:29:40 +1000897 const rowElement = this.getXRowAncestor_(range.startContainer);
898 if (!rowElement)
899 return;
900 const row = this.getLineStartRow_(rowElement);
John Macinnesfb683832013-07-22 14:46:30 -0400901 if (!row)
902 return;
903
904 var startPosition = this.getPositionWithOverflow_(row,
905 range.startContainer,
906 range.startOffset);
907 if (startPosition == -1)
908 return;
909 var endPosition = this.getPositionWithOverflow_(row,
910 range.endContainer,
911 range.endOffset);
912 if (endPosition == -1)
913 return;
914
John Macinnesfb683832013-07-22 14:46:30 -0400915 //Move start to the left.
916 var rowText = this.getLineText_(row);
Ricky Liang48f05cb2013-12-31 23:35:29 +0800917 var lineUpToRange = lib.wc.substring(rowText, 0, endPosition);
Robert Ginda5eba4562014-08-11 11:05:54 -0700918 var leftRegularExpression = new RegExp(leftMatch + insideMatch + "$");
John Macinnesfb683832013-07-22 14:46:30 -0400919 var expandedStart = lineUpToRange.search(leftRegularExpression);
920 if (expandedStart == -1 || expandedStart > startPosition)
921 return;
922
923 //Move end to the right.
Ricky Liang48f05cb2013-12-31 23:35:29 +0800924 var lineFromRange = lib.wc.substring(rowText, startPosition,
925 lib.wc.strWidth(rowText));
Robert Ginda5eba4562014-08-11 11:05:54 -0700926 var rightRegularExpression = new RegExp("^" + insideMatch + rightMatch);
John Macinnesfb683832013-07-22 14:46:30 -0400927 var found = lineFromRange.match(rightRegularExpression);
928 if (!found)
929 return;
Ricky Liang48f05cb2013-12-31 23:35:29 +0800930 var expandedEnd = startPosition + lib.wc.strWidth(found[0]);
John Macinnesfb683832013-07-22 14:46:30 -0400931 if (expandedEnd == -1 || expandedEnd < endPosition)
932 return;
933
934 this.setRange_(row, expandedStart, expandedEnd, range);
935 selection.addRange(range);
936};
Mike Frysingera2cacaa2017-11-29 13:51:09 -0800937
938/**
John Lincae9b732018-03-08 13:56:35 +0800939 * Expands selection to surrounding string using the user's settings.
940 *
941 * @param {Selection} selection Selection to expand.
942 */
943hterm.Screen.prototype.expandSelection = function(selection) {
944 this.expandSelectionWithWordBreakMatches_(
945 selection,
946 this.wordBreakMatchLeft,
947 this.wordBreakMatchRight,
948 this.wordBreakMatchMiddle);
Mike Frysinger8416e0a2017-05-17 09:09:46 -0400949};
John Lincae9b732018-03-08 13:56:35 +0800950
951/**
952 * Expands selection to surrounding URL using a set of fixed match settings.
953 *
954 * @param {Selection} selection Selection to expand.
955 */
956hterm.Screen.prototype.expandSelectionForUrl = function(selection) {
957 this.expandSelectionWithWordBreakMatches_(
958 selection,
959 "[^\\s\\[\\](){}<>\"'\\^!@#$%&*,;:`]",
960 "[^\\s\\[\\](){}<>\"'\\^!@#$%&*,;:~.`]",
961 "[^\\s\\[\\](){}<>\"'\\^]*");
962};
963
964/**
Mike Frysingera2cacaa2017-11-29 13:51:09 -0800965 * Save the current cursor state to the corresponding screens.
966 *
967 * @param {hterm.VT} vt The VT object to read graphic codeset details from.
968 */
969hterm.Screen.prototype.saveCursorAndState = function(vt) {
970 this.cursorState_.save(vt);
971};
972
973/**
974 * Restore the saved cursor state in the corresponding screens.
975 *
976 * @param {hterm.VT} vt The VT object to write graphic codeset details to.
977 */
978hterm.Screen.prototype.restoreCursorAndState = function(vt) {
979 this.cursorState_.restore(vt);
980};
981
982/**
983 * Track all the things related to the current "cursor".
984 *
985 * The set of things saved & restored here is defined by DEC:
986 * https://vt100.net/docs/vt510-rm/DECSC.html
987 * - Cursor position
988 * - Character attributes set by the SGR command
989 * - Character sets (G0, G1, G2, or G3) currently in GL and GR
990 * - Wrap flag (autowrap or no autowrap)
991 * - State of origin mode (DECOM)
992 * - Selective erase attribute
993 * - Any single shift 2 (SS2) or single shift 3 (SS3) functions sent
994 *
995 * These are done on a per-screen basis.
996 */
997hterm.Screen.CursorState = function(screen) {
998 this.screen_ = screen;
999 this.cursor = null;
1000 this.textAttributes = null;
1001 this.GL = this.GR = this.G0 = this.G1 = this.G2 = this.G3 = null;
1002};
1003
1004/**
1005 * Save all the cursor state.
1006 *
1007 * @param {hterm.VT} vt The VT object to read graphic codeset details from.
1008 */
1009hterm.Screen.CursorState.prototype.save = function(vt) {
1010 this.cursor = vt.terminal.saveCursor();
1011
1012 this.textAttributes = this.screen_.textAttributes.clone();
1013
1014 this.GL = vt.GL;
1015 this.GR = vt.GR;
1016
1017 this.G0 = vt.G0;
1018 this.G1 = vt.G1;
1019 this.G2 = vt.G2;
1020 this.G3 = vt.G3;
1021};
1022
1023/**
1024 * Restore the previously saved cursor state.
1025 *
1026 * @param {hterm.VT} vt The VT object to write graphic codeset details to.
1027 */
1028hterm.Screen.CursorState.prototype.restore = function(vt) {
1029 vt.terminal.restoreCursor(this.cursor);
1030
1031 // Cursor restore includes char attributes (bold/etc...), but does not change
1032 // the color palette (which are a terminal setting).
1033 const tattrs = this.textAttributes.clone();
1034 tattrs.colorPalette = this.screen_.textAttributes.colorPalette;
1035 tattrs.syncColors();
1036
1037 this.screen_.textAttributes = tattrs;
1038
1039 vt.GL = this.GL;
1040 vt.GR = this.GR;
1041
1042 vt.G0 = this.G0;
1043 vt.G1 = this.G1;
1044 vt.G2 = this.G2;
1045 vt.G3 = this.G3;
1046};