blob: f99899c19a54e4085a5dff0adb45f96f68be2de2 [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
Ricky Liang48f05cb2013-12-31 23:35:29 +08007lib.rtdep('lib.f', 'lib.wc',
rgindacbbd7482012-06-13 15:06:16 -07008 '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
Mike Frysingera2cacaa2017-11-29 13:51:09 -080065 // Saved state used by DECSC and related settings. This is only for saving
66 // and restoring specific state, not for the current/active state.
67 this.cursorState_ = new hterm.Screen.CursorState(this);
68
rginda8ba33642011-12-14 12:31:31 -080069 // The node containing the row that the cursor is positioned on.
70 this.cursorRowNode_ = null;
71
72 // The node containing the span of text that the cursor is positioned on.
73 this.cursorNode_ = null;
74
Ricky Liang48f05cb2013-12-31 23:35:29 +080075 // The offset in column width into cursorNode_ where the cursor is positioned.
rginda8ba33642011-12-14 12:31:31 -080076 this.cursorOffset_ = null;
Mike Frysinger664e9992017-05-19 01:24:24 -040077
78 // Regexes for expanding word selections.
79 this.wordBreakMatchLeft = null;
80 this.wordBreakMatchRight = null;
81 this.wordBreakMatchMiddle = null;
rginda8ba33642011-12-14 12:31:31 -080082};
83
84/**
85 * Return the screen size as an hterm.Size object.
86 *
87 * @return {hterm.Size} hterm.Size object representing the current number
88 * of rows and columns in this screen.
89 */
90hterm.Screen.prototype.getSize = function() {
91 return new hterm.Size(this.columnCount_, this.rowsArray.length);
92};
93
94/**
95 * Return the current number of rows in this screen.
96 *
97 * @return {integer} The number of rows in this screen.
98 */
99hterm.Screen.prototype.getHeight = function() {
100 return this.rowsArray.length;
101};
102
103/**
104 * Return the current number of columns in this screen.
105 *
106 * @return {integer} The number of columns in this screen.
107 */
108hterm.Screen.prototype.getWidth = function() {
109 return this.columnCount_;
110};
111
112/**
113 * Set the maximum number of columns per row.
114 *
rginda8ba33642011-12-14 12:31:31 -0800115 * @param {integer} count The maximum number of columns per row.
116 */
117hterm.Screen.prototype.setColumnCount = function(count) {
rginda2312fff2012-01-05 16:20:52 -0800118 this.columnCount_ = count;
119
rgindacbbd7482012-06-13 15:06:16 -0700120 if (this.cursorPosition.column >= count)
121 this.setCursorPosition(this.cursorPosition.row, count - 1);
rginda8ba33642011-12-14 12:31:31 -0800122};
123
124/**
125 * Remove the first row from the screen and return it.
126 *
127 * @return {HTMLElement} The first row in this screen.
128 */
129hterm.Screen.prototype.shiftRow = function() {
130 return this.shiftRows(1)[0];
rginda87b86462011-12-14 13:48:03 -0800131};
rginda8ba33642011-12-14 12:31:31 -0800132
133/**
134 * Remove rows from the top of the screen and return them as an array.
135 *
136 * @param {integer} count The number of rows to remove.
137 * @return {Array.<HTMLElement>} The selected rows.
138 */
139hterm.Screen.prototype.shiftRows = function(count) {
140 return this.rowsArray.splice(0, count);
141};
142
143/**
144 * Insert a row at the top of the screen.
145 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500146 * @param {HTMLElement} row The row to insert.
rginda8ba33642011-12-14 12:31:31 -0800147 */
148hterm.Screen.prototype.unshiftRow = function(row) {
149 this.rowsArray.splice(0, 0, row);
150};
151
152/**
153 * Insert rows at the top of the screen.
154 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500155 * @param {Array.<HTMLElement>} rows The rows to insert.
rginda8ba33642011-12-14 12:31:31 -0800156 */
157hterm.Screen.prototype.unshiftRows = function(rows) {
158 this.rowsArray.unshift.apply(this.rowsArray, rows);
159};
160
161/**
162 * Remove the last row from the screen and return it.
163 *
164 * @return {HTMLElement} The last row in this screen.
165 */
166hterm.Screen.prototype.popRow = function() {
167 return this.popRows(1)[0];
168};
169
170/**
171 * Remove rows from the bottom of the screen and return them as an array.
172 *
173 * @param {integer} count The number of rows to remove.
174 * @return {Array.<HTMLElement>} The selected rows.
175 */
176hterm.Screen.prototype.popRows = function(count) {
177 return this.rowsArray.splice(this.rowsArray.length - count, count);
178};
179
180/**
181 * Insert a row at the bottom of the screen.
182 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500183 * @param {HTMLElement} row The row to insert.
rginda8ba33642011-12-14 12:31:31 -0800184 */
185hterm.Screen.prototype.pushRow = function(row) {
186 this.rowsArray.push(row);
187};
188
189/**
190 * Insert rows at the bottom of the screen.
191 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500192 * @param {Array.<HTMLElement>} rows The rows to insert.
rginda8ba33642011-12-14 12:31:31 -0800193 */
194hterm.Screen.prototype.pushRows = function(rows) {
195 rows.push.apply(this.rowsArray, rows);
196};
197
198/**
Evan Jones2600d4f2016-12-06 09:29:36 -0500199 * Insert a row at the specified row of the screen.
rginda8ba33642011-12-14 12:31:31 -0800200 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500201 * @param {integer} index The index to insert the row.
202 * @param {HTMLElement} row The row to insert.
rginda8ba33642011-12-14 12:31:31 -0800203 */
204hterm.Screen.prototype.insertRow = function(index, row) {
205 this.rowsArray.splice(index, 0, row);
206};
207
208/**
Evan Jones2600d4f2016-12-06 09:29:36 -0500209 * Insert rows at the specified row of the screen.
rginda8ba33642011-12-14 12:31:31 -0800210 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500211 * @param {integer} index The index to insert the rows.
212 * @param {Array.<HTMLElement>} rows The rows to insert.
rginda8ba33642011-12-14 12:31:31 -0800213 */
214hterm.Screen.prototype.insertRows = function(index, rows) {
215 for (var i = 0; i < rows.length; i++) {
216 this.rowsArray.splice(index + i, 0, rows[i]);
217 }
218};
219
220/**
Evan Jones2600d4f2016-12-06 09:29:36 -0500221 * Remove a row from the screen and return it.
rginda8ba33642011-12-14 12:31:31 -0800222 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500223 * @param {integer} index The index of the row to remove.
rginda8ba33642011-12-14 12:31:31 -0800224 * @return {HTMLElement} The selected row.
225 */
226hterm.Screen.prototype.removeRow = function(index) {
227 return this.rowsArray.splice(index, 1)[0];
228};
229
230/**
231 * Remove rows from the bottom of the screen and return them as an array.
232 *
Evan Jones2600d4f2016-12-06 09:29:36 -0500233 * @param {integer} index The index to start removing rows.
rginda8ba33642011-12-14 12:31:31 -0800234 * @param {integer} count The number of rows to remove.
235 * @return {Array.<HTMLElement>} The selected rows.
236 */
237hterm.Screen.prototype.removeRows = function(index, count) {
238 return this.rowsArray.splice(index, count);
239};
240
241/**
242 * Invalidate the current cursor position.
243 *
rginda87b86462011-12-14 13:48:03 -0800244 * This sets this.cursorPosition to (0, 0) and clears out some internal
rginda8ba33642011-12-14 12:31:31 -0800245 * data.
246 *
247 * Attempting to insert or overwrite text while the cursor position is invalid
248 * will raise an obscure exception.
249 */
250hterm.Screen.prototype.invalidateCursorPosition = function() {
rginda87b86462011-12-14 13:48:03 -0800251 this.cursorPosition.move(0, 0);
rginda8ba33642011-12-14 12:31:31 -0800252 this.cursorRowNode_ = null;
253 this.cursorNode_ = null;
254 this.cursorOffset_ = null;
255};
256
257/**
rginda8ba33642011-12-14 12:31:31 -0800258 * Clear the contents of the cursor row.
rginda8ba33642011-12-14 12:31:31 -0800259 */
260hterm.Screen.prototype.clearCursorRow = function() {
261 this.cursorRowNode_.innerHTML = '';
rgindaa09e7332012-08-17 12:49:51 -0700262 this.cursorRowNode_.removeAttribute('line-overflow');
rginda8ba33642011-12-14 12:31:31 -0800263 this.cursorOffset_ = 0;
rginda8ba33642011-12-14 12:31:31 -0800264 this.cursorPosition.column = 0;
rginda2312fff2012-01-05 16:20:52 -0800265 this.cursorPosition.overflow = false;
Robert Ginda7fd57082012-09-25 14:41:47 -0700266
267 var text;
268 if (this.textAttributes.isDefault()) {
269 text = '';
270 } else {
271 text = lib.f.getWhitespace(this.columnCount_);
272 }
273
Zhu Qunying30d40712017-03-14 16:27:00 -0700274 // We shouldn't honor inverse colors when clearing an area, to match
275 // xterm's back color erase behavior.
Edoardo Spadolini2fd43642014-08-23 22:59:57 +0200276 var inverse = this.textAttributes.inverse;
277 this.textAttributes.inverse = false;
278 this.textAttributes.syncColors();
279
Robert Ginda7fd57082012-09-25 14:41:47 -0700280 var node = this.textAttributes.createContainer(text);
281 this.cursorRowNode_.appendChild(node);
282 this.cursorNode_ = node;
Edoardo Spadolini2fd43642014-08-23 22:59:57 +0200283
284 this.textAttributes.inverse = inverse;
285 this.textAttributes.syncColors();
rginda8ba33642011-12-14 12:31:31 -0800286};
287
288/**
rgindaa09e7332012-08-17 12:49:51 -0700289 * Mark the current row as having overflowed to the next line.
290 *
291 * The line overflow state is used when converting a range of rows into text.
292 * It makes it possible to recombine two or more overflow terminal rows into
293 * a single line.
294 *
295 * This is distinct from the cursor being in the overflow state. Cursor
296 * overflow indicates that printing at the cursor position will commit a
297 * line overflow, unless it is preceded by a repositioning of the cursor
298 * to a non-overflow state.
299 */
300hterm.Screen.prototype.commitLineOverflow = function() {
301 this.cursorRowNode_.setAttribute('line-overflow', true);
302};
303
304/**
rginda8ba33642011-12-14 12:31:31 -0800305 * Relocate the cursor to a give row and column.
306 *
307 * @param {integer} row The zero based row.
308 * @param {integer} column The zero based column.
309 */
310hterm.Screen.prototype.setCursorPosition = function(row, column) {
rginda11057d52012-04-25 12:29:56 -0700311 if (!this.rowsArray.length) {
312 console.warn('Attempt to set cursor position on empty screen.');
313 return;
314 }
315
rginda87b86462011-12-14 13:48:03 -0800316 if (row >= this.rowsArray.length) {
rgindacbbd7482012-06-13 15:06:16 -0700317 console.error('Row out of bounds: ' + row);
rginda87b86462011-12-14 13:48:03 -0800318 row = this.rowsArray.length - 1;
319 } else if (row < 0) {
rgindacbbd7482012-06-13 15:06:16 -0700320 console.error('Row out of bounds: ' + row);
rginda87b86462011-12-14 13:48:03 -0800321 row = 0;
322 }
323
324 if (column >= this.columnCount_) {
rgindacbbd7482012-06-13 15:06:16 -0700325 console.error('Column out of bounds: ' + column);
rginda87b86462011-12-14 13:48:03 -0800326 column = this.columnCount_ - 1;
327 } else if (column < 0) {
rgindacbbd7482012-06-13 15:06:16 -0700328 console.error('Column out of bounds: ' + column);
rginda87b86462011-12-14 13:48:03 -0800329 column = 0;
330 }
rginda8ba33642011-12-14 12:31:31 -0800331
rginda2312fff2012-01-05 16:20:52 -0800332 this.cursorPosition.overflow = false;
333
rginda8ba33642011-12-14 12:31:31 -0800334 var rowNode = this.rowsArray[row];
335 var node = rowNode.firstChild;
336
337 if (!node) {
338 node = rowNode.ownerDocument.createTextNode('');
339 rowNode.appendChild(node);
340 }
341
rgindaa19afe22012-01-25 15:40:22 -0800342 var currentColumn = 0;
343
rginda8ba33642011-12-14 12:31:31 -0800344 if (rowNode == this.cursorRowNode_) {
345 if (column >= this.cursorPosition.column - this.cursorOffset_) {
346 node = this.cursorNode_;
347 currentColumn = this.cursorPosition.column - this.cursorOffset_;
348 }
349 } else {
350 this.cursorRowNode_ = rowNode;
351 }
352
353 this.cursorPosition.move(row, column);
354
355 while (node) {
356 var offset = column - currentColumn;
Ricky Liang48f05cb2013-12-31 23:35:29 +0800357 var width = hterm.TextAttributes.nodeWidth(node);
358 if (!node.nextSibling || width > offset) {
rginda8ba33642011-12-14 12:31:31 -0800359 this.cursorNode_ = node;
360 this.cursorOffset_ = offset;
361 return;
362 }
363
Ricky Liang48f05cb2013-12-31 23:35:29 +0800364 currentColumn += width;
rginda8ba33642011-12-14 12:31:31 -0800365 node = node.nextSibling;
366 }
367};
368
369/**
rginda87b86462011-12-14 13:48:03 -0800370 * Set the provided selection object to be a caret selection at the current
371 * cursor position.
372 */
373hterm.Screen.prototype.syncSelectionCaret = function(selection) {
Rob Spies06533ba2014-04-24 11:20:37 -0700374 try {
375 selection.collapse(this.cursorNode_, this.cursorOffset_);
376 } catch (firefoxIgnoredException) {
377 // FF can throw an exception if the range is off, rather than just not
378 // performing the collapse.
379 }
rginda87b86462011-12-14 13:48:03 -0800380};
381
382/**
rgindaa19afe22012-01-25 15:40:22 -0800383 * Split a single node into two nodes at the given offset.
rginda8ba33642011-12-14 12:31:31 -0800384 *
rgindaa19afe22012-01-25 15:40:22 -0800385 * For example:
386 * Given the DOM fragment '<div><span>Hello World</span></div>', call splitNode_
Zhu Qunying30d40712017-03-14 16:27:00 -0700387 * passing the span and an offset of 6. This would modify the fragment to
rgindaa19afe22012-01-25 15:40:22 -0800388 * become: '<div><span>Hello </span><span>World</span></div>'. If the span
389 * had any attributes they would have been copied to the new span as well.
390 *
391 * The to-be-split node must have a container, so that the new node can be
392 * placed next to it.
393 *
394 * @param {HTMLNode} node The node to split.
395 * @param {integer} offset The offset into the node where the split should
396 * occur.
rginda8ba33642011-12-14 12:31:31 -0800397 */
rgindaa19afe22012-01-25 15:40:22 -0800398hterm.Screen.prototype.splitNode_ = function(node, offset) {
rginda35c456b2012-02-09 17:29:05 -0800399 var afterNode = node.cloneNode(false);
rgindaa19afe22012-01-25 15:40:22 -0800400
rginda35c456b2012-02-09 17:29:05 -0800401 var textContent = node.textContent;
Ricky Liang48f05cb2013-12-31 23:35:29 +0800402 node.textContent = hterm.TextAttributes.nodeSubstr(node, 0, offset);
403 afterNode.textContent = lib.wc.substr(textContent, offset);
rgindaa19afe22012-01-25 15:40:22 -0800404
Ricky Liang48f05cb2013-12-31 23:35:29 +0800405 if (afterNode.textContent)
406 node.parentNode.insertBefore(afterNode, node.nextSibling);
407 if (!node.textContent)
408 node.parentNode.removeChild(node);
rginda8ba33642011-12-14 12:31:31 -0800409};
410
411/**
rgindaa9abdd82012-08-06 18:05:09 -0700412 * Ensure that text is clipped and the cursor is clamped to the column count.
rgindaa19afe22012-01-25 15:40:22 -0800413 */
rgindaa9abdd82012-08-06 18:05:09 -0700414hterm.Screen.prototype.maybeClipCurrentRow = function() {
Ricky Liang48f05cb2013-12-31 23:35:29 +0800415 var width = hterm.TextAttributes.nodeWidth(this.cursorRowNode_);
416
417 if (width <= this.columnCount_) {
rgindaa9abdd82012-08-06 18:05:09 -0700418 // Current row does not need clipping, but may need clamping.
419 if (this.cursorPosition.column >= this.columnCount_) {
420 this.setCursorPosition(this.cursorPosition.row, this.columnCount_ - 1);
421 this.cursorPosition.overflow = true;
422 }
rgindaa19afe22012-01-25 15:40:22 -0800423
rgindaa9abdd82012-08-06 18:05:09 -0700424 return;
425 }
426
427 // Save off the current column so we can maybe restore it later.
428 var currentColumn = this.cursorPosition.column;
429
430 // Move the cursor to the final column.
431 this.setCursorPosition(this.cursorPosition.row, this.columnCount_ - 1);
432
433 // Remove any text that partially overflows.
Ricky Liang48f05cb2013-12-31 23:35:29 +0800434 width = hterm.TextAttributes.nodeWidth(this.cursorNode_);
435
436 if (this.cursorOffset_ < width - 1) {
437 this.cursorNode_.textContent = hterm.TextAttributes.nodeSubstr(
438 this.cursorNode_, 0, this.cursorOffset_ + 1);
rgindaa9abdd82012-08-06 18:05:09 -0700439 }
440
441 // Remove all nodes after the cursor.
rgindaa19afe22012-01-25 15:40:22 -0800442 var rowNode = this.cursorRowNode_;
443 var node = this.cursorNode_.nextSibling;
444
445 while (node) {
rgindaa19afe22012-01-25 15:40:22 -0800446 rowNode.removeChild(node);
447 node = this.cursorNode_.nextSibling;
448 }
449
Robert Ginda7fd57082012-09-25 14:41:47 -0700450 if (currentColumn < this.columnCount_) {
rgindaa9abdd82012-08-06 18:05:09 -0700451 // If the cursor was within the screen before we started then restore its
452 // position.
rgindaa19afe22012-01-25 15:40:22 -0800453 this.setCursorPosition(this.cursorPosition.row, currentColumn);
rgindaa9abdd82012-08-06 18:05:09 -0700454 } else {
455 // Otherwise leave it at the the last column in the overflow state.
456 this.cursorPosition.overflow = true;
rgindaa19afe22012-01-25 15:40:22 -0800457 }
rgindaa19afe22012-01-25 15:40:22 -0800458};
459
460/**
461 * Insert a string at the current character position using the current
462 * text attributes.
463 *
rgindaa09e7332012-08-17 12:49:51 -0700464 * You must call maybeClipCurrentRow() after in order to clip overflowed
465 * text and clamp the cursor.
466 *
467 * It is also up to the caller to properly maintain the line overflow state
468 * using hterm.Screen..commitLineOverflow().
rginda8ba33642011-12-14 12:31:31 -0800469 */
Mike Frysinger6380bed2017-08-24 18:46:39 -0400470hterm.Screen.prototype.insertString = function(str, wcwidth=undefined) {
rgindaa19afe22012-01-25 15:40:22 -0800471 var cursorNode = this.cursorNode_;
472 var cursorNodeText = cursorNode.textContent;
rginda8ba33642011-12-14 12:31:31 -0800473
Robert Gindaa21dfb32013-10-31 14:17:45 -0700474 this.cursorRowNode_.removeAttribute('line-overflow');
475
Ricky Liang48f05cb2013-12-31 23:35:29 +0800476 // We may alter the width of the string by prepending some missing
477 // whitespaces, so we need to record the string width ahead of time.
Mike Frysinger6380bed2017-08-24 18:46:39 -0400478 if (wcwidth === undefined)
479 wcwidth = lib.wc.strWidth(str);
rginda8ba33642011-12-14 12:31:31 -0800480
rgindaa19afe22012-01-25 15:40:22 -0800481 // No matter what, before this function exits the cursor column will have
482 // moved this much.
Mike Frysinger6380bed2017-08-24 18:46:39 -0400483 this.cursorPosition.column += wcwidth;
rginda8ba33642011-12-14 12:31:31 -0800484
rgindaa19afe22012-01-25 15:40:22 -0800485 // Local cache of the cursor offset.
486 var offset = this.cursorOffset_;
rginda8ba33642011-12-14 12:31:31 -0800487
rgindaa19afe22012-01-25 15:40:22 -0800488 // Reverse offset is the offset measured from the end of the string.
489 // Zero implies that the cursor is at the end of the cursor node.
Ricky Liang48f05cb2013-12-31 23:35:29 +0800490 var reverseOffset = hterm.TextAttributes.nodeWidth(cursorNode) - offset;
rgindaa19afe22012-01-25 15:40:22 -0800491
492 if (reverseOffset < 0) {
493 // A negative reverse offset means the cursor is positioned past the end
494 // of the characters on this line. We'll need to insert the missing
495 // whitespace.
rgindacbbd7482012-06-13 15:06:16 -0700496 var ws = lib.f.getWhitespace(-reverseOffset);
rgindaa19afe22012-01-25 15:40:22 -0800497
Brad Town7de83302015-03-12 02:10:32 -0700498 // This whitespace should be completely unstyled. Underline, background
499 // color, and strikethrough would be visible on whitespace, so we can't use
500 // one of those spans to hold the text.
Edoardo Spadolini198828a2014-08-08 00:22:51 +0200501 if (!(this.textAttributes.underline ||
Brad Town7de83302015-03-12 02:10:32 -0700502 this.textAttributes.strikethrough ||
Edoardo Spadolini198828a2014-08-08 00:22:51 +0200503 this.textAttributes.background ||
504 this.textAttributes.wcNode ||
Mike Frysinger1e98c0f2017-08-15 01:21:31 -0400505 !this.textAttributes.asciiNode ||
Edoardo Spadolini198828a2014-08-08 00:22:51 +0200506 this.textAttributes.tileData != null)) {
rgindaa19afe22012-01-25 15:40:22 -0800507 // Best case scenario, we can just pretend the spaces were part of the
508 // original string.
509 str = ws + str;
Mike Frysinger6a4f2412017-08-31 01:11:25 -0400510 } else if (cursorNode.nodeType == Node.TEXT_NODE ||
Ricky Liang48f05cb2013-12-31 23:35:29 +0800511 !(cursorNode.wcNode ||
Mike Frysinger1e98c0f2017-08-15 01:21:31 -0400512 !cursorNode.asciiNode ||
Edoardo Spadolini198828a2014-08-08 00:22:51 +0200513 cursorNode.tileNode ||
Ricky Liang48f05cb2013-12-31 23:35:29 +0800514 cursorNode.style.textDecoration ||
Mike Frysinger09c54f42017-12-15 01:12:30 -0500515 cursorNode.style.textDecorationStyle ||
516 cursorNode.style.textDecorationLine ||
rgindaa19afe22012-01-25 15:40:22 -0800517 cursorNode.style.backgroundColor)) {
518 // Second best case, the current node is able to hold the whitespace.
519 cursorNode.textContent = (cursorNodeText += ws);
520 } else {
521 // Worst case, we have to create a new node to hold the whitespace.
522 var wsNode = cursorNode.ownerDocument.createTextNode(ws);
523 this.cursorRowNode_.insertBefore(wsNode, cursorNode.nextSibling);
524 this.cursorNode_ = cursorNode = wsNode;
525 this.cursorOffset_ = offset = -reverseOffset;
526 cursorNodeText = ws;
527 }
528
529 // We now know for sure that we're at the last character of the cursor node.
530 reverseOffset = 0;
rginda8ba33642011-12-14 12:31:31 -0800531 }
532
rgindaa19afe22012-01-25 15:40:22 -0800533 if (this.textAttributes.matchesContainer(cursorNode)) {
534 // The new text can be placed directly in the cursor node.
535 if (reverseOffset == 0) {
536 cursorNode.textContent = cursorNodeText + str;
537 } else if (offset == 0) {
538 cursorNode.textContent = str + cursorNodeText;
539 } else {
Ricky Liang48f05cb2013-12-31 23:35:29 +0800540 cursorNode.textContent =
541 hterm.TextAttributes.nodeSubstr(cursorNode, 0, offset) +
542 str + hterm.TextAttributes.nodeSubstr(cursorNode, offset);
rgindaa19afe22012-01-25 15:40:22 -0800543 }
rginda8ba33642011-12-14 12:31:31 -0800544
Mike Frysinger6380bed2017-08-24 18:46:39 -0400545 this.cursorOffset_ += wcwidth;
rgindaa19afe22012-01-25 15:40:22 -0800546 return;
rginda87b86462011-12-14 13:48:03 -0800547 }
548
rgindaa19afe22012-01-25 15:40:22 -0800549 // The cursor node is the wrong style for the new text. If we're at the
550 // beginning or end of the cursor node, then the adjacent node is also a
551 // potential candidate.
rginda8ba33642011-12-14 12:31:31 -0800552
rgindaa19afe22012-01-25 15:40:22 -0800553 if (offset == 0) {
554 // At the beginning of the cursor node, the check the previous sibling.
555 var previousSibling = cursorNode.previousSibling;
556 if (previousSibling &&
557 this.textAttributes.matchesContainer(previousSibling)) {
558 previousSibling.textContent += str;
559 this.cursorNode_ = previousSibling;
Ricky Liang48f05cb2013-12-31 23:35:29 +0800560 this.cursorOffset_ = lib.wc.strWidth(previousSibling.textContent);
rgindaa19afe22012-01-25 15:40:22 -0800561 return;
562 }
563
564 var newNode = this.textAttributes.createContainer(str);
565 this.cursorRowNode_.insertBefore(newNode, cursorNode);
566 this.cursorNode_ = newNode;
Mike Frysinger6380bed2017-08-24 18:46:39 -0400567 this.cursorOffset_ = wcwidth;
rgindaa19afe22012-01-25 15:40:22 -0800568 return;
569 }
570
571 if (reverseOffset == 0) {
572 // At the end of the cursor node, the check the next sibling.
573 var nextSibling = cursorNode.nextSibling;
574 if (nextSibling &&
575 this.textAttributes.matchesContainer(nextSibling)) {
576 nextSibling.textContent = str + nextSibling.textContent;
577 this.cursorNode_ = nextSibling;
Ricky Liang48f05cb2013-12-31 23:35:29 +0800578 this.cursorOffset_ = lib.wc.strWidth(str);
rgindaa19afe22012-01-25 15:40:22 -0800579 return;
580 }
581
582 var newNode = this.textAttributes.createContainer(str);
583 this.cursorRowNode_.insertBefore(newNode, nextSibling);
584 this.cursorNode_ = newNode;
585 // We specifically need to include any missing whitespace here, since it's
586 // going in a new node.
Ricky Liang48f05cb2013-12-31 23:35:29 +0800587 this.cursorOffset_ = hterm.TextAttributes.nodeWidth(newNode);
rgindaa19afe22012-01-25 15:40:22 -0800588 return;
589 }
590
591 // Worst case, we're somewhere in the middle of the cursor node. We'll
592 // have to split it into two nodes and insert our new container in between.
593 this.splitNode_(cursorNode, offset);
594 var newNode = this.textAttributes.createContainer(str);
595 this.cursorRowNode_.insertBefore(newNode, cursorNode.nextSibling);
596 this.cursorNode_ = newNode;
Mike Frysinger6380bed2017-08-24 18:46:39 -0400597 this.cursorOffset_ = wcwidth;
rgindaa19afe22012-01-25 15:40:22 -0800598};
599
600/**
rginda8ba33642011-12-14 12:31:31 -0800601 * Overwrite the text at the current cursor position.
602 *
rgindaa09e7332012-08-17 12:49:51 -0700603 * You must call maybeClipCurrentRow() after in order to clip overflowed
604 * text and clamp the cursor.
605 *
606 * It is also up to the caller to properly maintain the line overflow state
607 * using hterm.Screen..commitLineOverflow().
rginda8ba33642011-12-14 12:31:31 -0800608 */
Mike Frysinger6380bed2017-08-24 18:46:39 -0400609hterm.Screen.prototype.overwriteString = function(str, wcwidth=undefined) {
rginda8ba33642011-12-14 12:31:31 -0800610 var maxLength = this.columnCount_ - this.cursorPosition.column;
611 if (!maxLength)
rgindaa19afe22012-01-25 15:40:22 -0800612 return [str];
613
Mike Frysinger6380bed2017-08-24 18:46:39 -0400614 if (wcwidth === undefined)
615 wcwidth = lib.wc.strWidth(str);
616
Ricky Liang48f05cb2013-12-31 23:35:29 +0800617 if (this.textAttributes.matchesContainer(this.cursorNode_) &&
618 this.cursorNode_.textContent.substr(this.cursorOffset_) == str) {
rgindaa19afe22012-01-25 15:40:22 -0800619 // This overwrite would be a no-op, just move the cursor and return.
Mike Frysinger6380bed2017-08-24 18:46:39 -0400620 this.cursorOffset_ += wcwidth;
621 this.cursorPosition.column += wcwidth;
rgindaa19afe22012-01-25 15:40:22 -0800622 return;
623 }
rginda8ba33642011-12-14 12:31:31 -0800624
Mike Frysinger6380bed2017-08-24 18:46:39 -0400625 this.deleteChars(Math.min(wcwidth, maxLength));
626 this.insertString(str, wcwidth);
rginda8ba33642011-12-14 12:31:31 -0800627};
628
629/**
630 * Forward-delete one or more characters at the current cursor position.
631 *
632 * Text to the right of the deleted characters is shifted left. Only affects
633 * characters on the same row as the cursor.
634 *
Ricky Liang48f05cb2013-12-31 23:35:29 +0800635 * @param {integer} count The column width of characters to delete. This is
636 * clamped to the column width minus the cursor column.
637 * @return {integer} The column width of the characters actually deleted.
rginda8ba33642011-12-14 12:31:31 -0800638 */
639hterm.Screen.prototype.deleteChars = function(count) {
640 var node = this.cursorNode_;
641 var offset = this.cursorOffset_;
642
Robert Ginda7fd57082012-09-25 14:41:47 -0700643 var currentCursorColumn = this.cursorPosition.column;
644 count = Math.min(count, this.columnCount_ - currentCursorColumn);
645 if (!count)
646 return 0;
647
648 var rv = count;
Ricky Liang48f05cb2013-12-31 23:35:29 +0800649 var startLength, endLength;
rgindaa19afe22012-01-25 15:40:22 -0800650
rginda8ba33642011-12-14 12:31:31 -0800651 while (node && count) {
Mike Frysinger859bcbd2017-08-28 23:48:43 -0400652 // Sanity check so we don't loop forever, but we don't also go quietly.
653 if (count < 0) {
654 console.error(`Deleting ${rv} chars went negative: ${count}`);
655 break;
656 }
657
Ricky Liang48f05cb2013-12-31 23:35:29 +0800658 startLength = hterm.TextAttributes.nodeWidth(node);
659 node.textContent = hterm.TextAttributes.nodeSubstr(node, 0, offset) +
660 hterm.TextAttributes.nodeSubstr(node, offset + count);
661 endLength = hterm.TextAttributes.nodeWidth(node);
Mike Frysinger859bcbd2017-08-28 23:48:43 -0400662
663 // Deal with splitting wide characters. There are two ways: we could delete
664 // the first column or the second column. In both cases, we delete the wide
665 // character and replace one of the columns with a space (since the other
666 // was deleted). If there are more chars to delete, the next loop will pick
667 // up the slack.
668 if (node.wcNode && offset < startLength &&
669 ((endLength && startLength == endLength) || (!endLength && offset == 1))) {
Ricky Liang48f05cb2013-12-31 23:35:29 +0800670 // No characters were deleted when there should be. We're probably trying
671 // to delete one column width from a wide character node. We remove the
672 // wide character node here and replace it with a single space.
673 var spaceNode = this.textAttributes.createContainer(' ');
Mike Frysinger859bcbd2017-08-28 23:48:43 -0400674 node.parentNode.insertBefore(spaceNode, offset ? node : node.nextSibling);
Ricky Liang48f05cb2013-12-31 23:35:29 +0800675 node.textContent = '';
676 endLength = 0;
677 count -= 1;
Mike Frysinger859bcbd2017-08-28 23:48:43 -0400678 } else
679 count -= startLength - endLength;
rginda8ba33642011-12-14 12:31:31 -0800680
Ricky Liang48f05cb2013-12-31 23:35:29 +0800681 var nextNode = node.nextSibling;
682 if (endLength == 0 && node != this.cursorNode_) {
683 node.parentNode.removeChild(node);
684 }
685 node = nextNode;
rginda8ba33642011-12-14 12:31:31 -0800686 offset = 0;
687 }
Robert Ginda7fd57082012-09-25 14:41:47 -0700688
Ricky Liang48f05cb2013-12-31 23:35:29 +0800689 // Remove this.cursorNode_ if it is an empty non-text node.
Mike Frysinger6a4f2412017-08-31 01:11:25 -0400690 if (this.cursorNode_.nodeType != Node.TEXT_NODE &&
691 !this.cursorNode_.textContent) {
Ricky Liang48f05cb2013-12-31 23:35:29 +0800692 var cursorNode = this.cursorNode_;
693 if (cursorNode.previousSibling) {
694 this.cursorNode_ = cursorNode.previousSibling;
695 this.cursorOffset_ = hterm.TextAttributes.nodeWidth(
696 cursorNode.previousSibling);
697 } else if (cursorNode.nextSibling) {
698 this.cursorNode_ = cursorNode.nextSibling;
699 this.cursorOffset_ = 0;
700 } else {
701 var emptyNode = this.cursorRowNode_.ownerDocument.createTextNode('');
702 this.cursorRowNode_.appendChild(emptyNode);
703 this.cursorNode_ = emptyNode;
704 this.cursorOffset_ = 0;
705 }
706 this.cursorRowNode_.removeChild(cursorNode);
707 }
708
Robert Ginda7fd57082012-09-25 14:41:47 -0700709 return rv;
rginda8ba33642011-12-14 12:31:31 -0800710};
John Macinnesfb683832013-07-22 14:46:30 -0400711
712/**
713 * Finds first X-ROW of a line containing specified X-ROW.
714 * Used to support line overflow.
715 *
716 * @param {Node} row X-ROW to begin search for first row of line.
717 * @return {Node} The X-ROW that is at the beginning of the line.
718 **/
719hterm.Screen.prototype.getLineStartRow_ = function(row) {
720 while (row.previousSibling &&
721 row.previousSibling.hasAttribute('line-overflow')) {
722 row = row.previousSibling;
723 }
724 return row;
725};
726
727/**
728 * Gets text of a line beginning with row.
729 * Supports line overflow.
730 *
731 * @param {Node} row First X-ROW of line.
732 * @return {string} Text content of line.
733 **/
734hterm.Screen.prototype.getLineText_ = function(row) {
735 var rowText = "";
736 while (row) {
737 rowText += row.textContent;
738 if (row.hasAttribute('line-overflow')) {
739 row = row.nextSibling;
740 } else {
741 break;
742 }
743 }
744 return rowText;
745};
746
747/**
748 * Returns X-ROW that is ancestor of the node.
749 *
750 * @param {Node} node Node to get X-ROW ancestor for.
751 * @return {Node} X-ROW ancestor of node, or null if not found.
752 **/
753hterm.Screen.prototype.getXRowAncestor_ = function(node) {
754 while (node) {
755 if (node.nodeName === 'X-ROW')
756 break;
757 node = node.parentNode;
758 }
759 return node;
760};
761
762/**
763 * Returns position within line of character at offset within node.
764 * Supports line overflow.
765 *
766 * @param {Node} row X-ROW at beginning of line.
767 * @param {Node} node Node to get position of.
768 * @param {integer} offset Offset into node.
769 *
770 * @return {integer} Position within line of character at offset within node.
771 **/
772hterm.Screen.prototype.getPositionWithOverflow_ = function(row, node, offset) {
773 if (!node)
774 return -1;
775 var ancestorRow = this.getXRowAncestor_(node);
776 if (!ancestorRow)
777 return -1;
778 var position = 0;
779 while (ancestorRow != row) {
Ricky Liang48f05cb2013-12-31 23:35:29 +0800780 position += hterm.TextAttributes.nodeWidth(row);
John Macinnesfb683832013-07-22 14:46:30 -0400781 if (row.hasAttribute('line-overflow') && row.nextSibling) {
782 row = row.nextSibling;
783 } else {
784 return -1;
785 }
786 }
787 return position + this.getPositionWithinRow_(row, node, offset);
788};
789
790/**
791 * Returns position within row of character at offset within node.
792 * Does not support line overflow.
793 *
794 * @param {Node} row X-ROW to get position within.
795 * @param {Node} node Node to get position for.
796 * @param {integer} offset Offset within node to get position for.
797 * @return {integer} Position within row of character at offset within node.
798 **/
799hterm.Screen.prototype.getPositionWithinRow_ = function(row, node, offset) {
800 if (node.parentNode != row) {
Mike Frysinger498192d2017-06-26 18:23:31 -0400801 // If we traversed to the top node, then there's nothing to find here.
802 if (node.parentNode == null)
803 return -1;
804
John Macinnesfb683832013-07-22 14:46:30 -0400805 return this.getPositionWithinRow_(node.parentNode, node, offset) +
806 this.getPositionWithinRow_(row, node.parentNode, 0);
807 }
808 var position = 0;
809 for (var i = 0; i < row.childNodes.length; i++) {
810 var currentNode = row.childNodes[i];
811 if (currentNode == node)
812 return position + offset;
Ricky Liang48f05cb2013-12-31 23:35:29 +0800813 position += hterm.TextAttributes.nodeWidth(currentNode);
John Macinnesfb683832013-07-22 14:46:30 -0400814 }
815 return -1;
816};
817
818/**
819 * Returns the node and offset corresponding to position within line.
820 * Supports line overflow.
821 *
822 * @param {Node} row X-ROW at beginning of line.
823 * @param {integer} position Position within line to retrieve node and offset.
824 * @return {Array} Two element array containing node and offset respectively.
825 **/
826hterm.Screen.prototype.getNodeAndOffsetWithOverflow_ = function(row, position) {
Ricky Liang48f05cb2013-12-31 23:35:29 +0800827 while (row && position > hterm.TextAttributes.nodeWidth(row)) {
John Macinnesfb683832013-07-22 14:46:30 -0400828 if (row.hasAttribute('line-overflow') && row.nextSibling) {
Ricky Liang48f05cb2013-12-31 23:35:29 +0800829 position -= hterm.TextAttributes.nodeWidth(row);
John Macinnesfb683832013-07-22 14:46:30 -0400830 row = row.nextSibling;
831 } else {
832 return -1;
833 }
834 }
835 return this.getNodeAndOffsetWithinRow_(row, position);
836};
837
838/**
839 * Returns the node and offset corresponding to position within row.
840 * Does not support line overflow.
841 *
842 * @param {Node} row X-ROW to get position within.
843 * @param {integer} position Position within row to retrieve node and offset.
844 * @return {Array} Two element array containing node and offset respectively.
845 **/
846hterm.Screen.prototype.getNodeAndOffsetWithinRow_ = function(row, position) {
847 for (var i = 0; i < row.childNodes.length; i++) {
848 var node = row.childNodes[i];
Ricky Liang48f05cb2013-12-31 23:35:29 +0800849 var nodeTextWidth = hterm.TextAttributes.nodeWidth(node);
850 if (position <= nodeTextWidth) {
John Macinnesfb683832013-07-22 14:46:30 -0400851 if (node.nodeName === 'SPAN') {
852 /** Drill down to node contained by SPAN. **/
853 return this.getNodeAndOffsetWithinRow_(node, position);
854 } else {
855 return [node, position];
856 }
857 }
Ricky Liang48f05cb2013-12-31 23:35:29 +0800858 position -= nodeTextWidth;
John Macinnesfb683832013-07-22 14:46:30 -0400859 }
860 return null;
861};
862
863/**
864 * Returns the node and offset corresponding to position within line.
865 * Supports line overflow.
866 *
867 * @param {Node} row X-ROW at beginning of line.
868 * @param {integer} start Start position of range within line.
869 * @param {integer} end End position of range within line.
870 * @param {Range} range Range to modify.
871 **/
872hterm.Screen.prototype.setRange_ = function(row, start, end, range) {
873 var startNodeAndOffset = this.getNodeAndOffsetWithOverflow_(row, start);
874 if (startNodeAndOffset == null)
875 return;
876 var endNodeAndOffset = this.getNodeAndOffsetWithOverflow_(row, end);
877 if (endNodeAndOffset == null)
878 return;
879 range.setStart(startNodeAndOffset[0], startNodeAndOffset[1]);
880 range.setEnd(endNodeAndOffset[0], endNodeAndOffset[1]);
881};
882
883/**
John Lincae9b732018-03-08 13:56:35 +0800884 * Expands selection to surrounding string with word break matches.
John Macinnesfb683832013-07-22 14:46:30 -0400885 *
John Macinnesfb683832013-07-22 14:46:30 -0400886 * @param {Selection} selection Selection to expand.
John Lincae9b732018-03-08 13:56:35 +0800887 * @param {string} leftMatch left word break match.
888 * @param {string} rightMatch right word break match.
889 * @param {string} insideMatch inside word break match.
890 */
891hterm.Screen.prototype.expandSelectionWithWordBreakMatches_ =
892 function(selection, leftMatch, rightMatch, insideMatch) {
John Macinnesfb683832013-07-22 14:46:30 -0400893 if (!selection)
894 return;
895
896 var range = selection.getRangeAt(0);
897 if (!range || range.toString().match(/\s/))
898 return;
899
900 var row = this.getLineStartRow_(this.getXRowAncestor_(range.startContainer));
901 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};