Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8)

Side by Side Diff: chrome/browser/resources/hterm/js/screen.js

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

Powered by Google App Engine
This is Rietveld 408576698