OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. Use | |
2 // of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 /** | |
6 * Constructor for the Terminal class. | |
7 * | |
8 * A Terminal pulls together the hterm.ScrollPort, hterm.Screen and hterm.VT100 | |
9 * classes to provide the complete terminal functionality. | |
10 * | |
11 * There are a number of lower-level Terminal methods that can be called | |
12 * directly to manipulate the cursor, text, scroll region, and other terminal | |
13 * attributes. However, the primary method is interpret(), which parses VT | |
14 * escape sequences and invokes the appropriate Terminal methods. | |
15 * | |
16 * This class was heavily influenced by Cory Maccarrone's Framebuffer class. | |
17 * | |
18 * TODO(rginda): Eventually we're going to need to support characters which are | |
19 * displayed twice as wide as standard latin characters. This is to support | |
20 * CJK (and possibly other character sets). | |
21 */ | |
22 hterm.Terminal = function() { | |
23 // Two screen instances. | |
24 this.primaryScreen_ = new hterm.Screen(); | |
25 this.alternateScreen_ = new hterm.Screen(); | |
26 | |
27 // The "current" screen. | |
28 this.screen_ = this.primaryScreen_; | |
29 | |
30 // The VT escape sequence interpreter. | |
31 this.vt100_ = new hterm.VT100(this); | |
32 | |
33 // The local notion of the screen size. ScreenBuffers also have a size which | |
34 // indicates their present size. During size changes, the two may disagree. | |
35 // Also, the inactive screen's size is not altered until it is made the active | |
36 // screen. | |
37 this.screenSize = new hterm.Size(0, 0); | |
38 | |
39 // The pixel dimensions of a single character on the screen. | |
40 this.characterSize_ = new hterm.Size(0, 0); | |
41 | |
42 // The scroll port we'll be using to display the visible rows. | |
43 this.scrollPort_ = new hterm.ScrollPort(this, 15); | |
44 this.scrollPort_.subscribe('resize', this.onResize_.bind(this)); | |
45 this.scrollPort_.subscribe('scroll', this.onScroll_.bind(this)); | |
46 | |
47 // The rows that have scrolled off screen and are no longer addressable. | |
48 this.scrollbackRows_ = []; | |
49 | |
50 // The VT's notion of the top and bottom rows. Used during some VT | |
51 // cursor positioning and scrolling commands. | |
52 this.vtScrollTop_ = null; | |
53 this.vtScrollBottom_ = null; | |
54 | |
55 // The timeout handle for the blinking cursor, or null if the cursor is not | |
56 // blinking. | |
57 this.cursorTimer_ = null; | |
58 | |
59 // The DIV element for the visible cursor. | |
60 this.cursorNode_ = null; | |
61 | |
62 // The default colors for text with no other color attributes. | |
63 this.backgroundColor = 'black'; | |
64 this.foregroundColor = 'white'; | |
65 | |
66 // The color of the cursor. | |
67 this.cursorColor = 'rgba(255,0,0,0.5)'; | |
68 | |
69 // The current mode bits for the terminal. | |
70 this.options_ = new hterm.Options(); | |
71 }; | |
72 | |
73 /** | |
74 * Methods called by Cory's vt100 interpreter which we haven't implemented yet. | |
75 */ | |
76 hterm.Terminal.prototype.reset = | |
77 hterm.Terminal.prototype.clearColorAndAttributes = | |
78 hterm.Terminal.prototype.setForegroundColor256 = | |
79 hterm.Terminal.prototype.setBackgroundColor256 = | |
80 hterm.Terminal.prototype.setForegroundColor = | |
81 hterm.Terminal.prototype.setBackgroundColor = | |
82 hterm.Terminal.prototype.setAttributes = | |
83 hterm.Terminal.prototype.resize = | |
84 hterm.Terminal.prototype.setSpecialCharactersEnabled = | |
85 hterm.Terminal.prototype.setTabStopAtCursor = | |
86 hterm.Terminal.prototype.clearTabStops = | |
87 hterm.Terminal.prototype.saveCursor = | |
88 hterm.Terminal.prototype.restoreCursor = | |
89 hterm.Terminal.prototype.reverseLineFeed = function() { | |
90 throw 'NOT IMPLEMENTED'; | |
91 }; | |
92 | |
93 /** | |
94 * Interpret a sequence of characters. | |
95 * | |
96 * Incomplete escape sequences are buffered until the next call. | |
97 * | |
98 * @param {string} str Sequence of characters to interpret or pass through. | |
99 */ | |
100 hterm.Terminal.prototype.interpret = function(str) { | |
101 this.vt100_.interpretString(str); | |
102 this.scheduleSyncCursorPosition_(); | |
103 }; | |
104 | |
105 /** | |
106 * Take over the given DIV for use as the terminal display. | |
107 * | |
108 * @param {HTMLDivElement} div The div to use as the terminal display. | |
109 */ | |
110 hterm.Terminal.prototype.decorate = function(div) { | |
111 this.scrollPort_.decorate(div); | |
112 this.document_ = this.scrollPort_.getDocument(); | |
113 | |
114 // Get character dimensions from the scrollPort. | |
115 this.characterSize_.height = this.scrollPort_.getRowHeight(); | |
116 this.characterSize_.width = this.scrollPort_.getCharacterWidth(); | |
117 | |
118 this.cursorNode_ = this.document_.createElement('div'); | |
119 this.cursorNode_.style.cssText = | |
120 ('position: absolute;' + | |
121 'display: none;' + | |
122 'width: ' + this.characterSize_.width + 'px;' + | |
123 'height: ' + this.characterSize_.height + 'px;' + | |
124 'background-color: ' + this.cursorColor); | |
125 this.document_.body.appendChild(this.cursorNode_); | |
126 | |
127 this.setReverseVideo(false); | |
128 }; | |
129 | |
130 /** | |
131 * Return the HTML Element for a given row index. | |
132 * | |
133 * This is a method from the RowProvider interface. The ScrollPort uses | |
134 * it to fetch rows on demand as they are scrolled into view. | |
135 * | |
136 * TODO(rginda): Consider saving scrollback rows as (HTML source, text content) | |
137 * pairs to conserve memory. | |
138 * | |
139 * @param {integer} index The zero-based row index, measured relative to the | |
140 * start of the scrollback buffer. On-screen rows will always have the | |
141 * largest indicies. | |
142 * @return {HTMLElement} The 'x-row' element containing for the requested row. | |
143 */ | |
144 hterm.Terminal.prototype.getRowNode = function(index) { | |
145 if (index < this.scrollbackRows_.length) | |
146 return this.scrollbackRows_[index]; | |
147 | |
148 var screenIndex = index - this.scrollbackRows_.length; | |
149 return this.screen_.rowsArray[screenIndex]; | |
150 }; | |
151 | |
152 /** | |
153 * Return the text content for a given range of rows. | |
154 * | |
155 * This is a method from the RowProvider interface. The ScrollPort uses | |
156 * it to fetch text content on demand when the user attempts to copy their | |
157 * selection to the clipboard. | |
158 * | |
159 * @param {integer} start The zero-based row index to start from, measured | |
160 * relative to the start of the scrollback buffer. On-screen rows will | |
161 * always have the largest indicies. | |
162 * @param {integer} end The zero-based row index to end on, measured | |
163 * relative to the start of the scrollback buffer. | |
164 * @return {string} A single string containing the text value of the range of | |
165 * rows. Lines will be newline delimited, with no trailing newline. | |
166 */ | |
167 hterm.Terminal.prototype.getRowsText = function(start, end) { | |
168 var ary = []; | |
169 for (var i = start; i < end; i++) { | |
170 var node = this.getRowNode(i); | |
171 ary.push(node.textContent); | |
172 } | |
173 | |
174 return ary.join('\n'); | |
175 }; | |
176 | |
177 /** | |
178 * Return the text content for a given row. | |
179 * | |
180 * This is a method from the RowProvider interface. The ScrollPort uses | |
181 * it to fetch text content on demand when the user attempts to copy their | |
182 * selection to the clipboard. | |
183 * | |
184 * @param {integer} index The zero-based row index to return, measured | |
185 * relative to the start of the scrollback buffer. On-screen rows will | |
186 * always have the largest indicies. | |
187 * @return {string} A string containing the text value of the selected row. | |
188 */ | |
189 hterm.Terminal.prototype.getRowText = function(index) { | |
190 var node = this.getRowNode(index); | |
191 return row.textContent; | |
192 }; | |
193 | |
194 /** | |
195 * Return the total number of rows in the addressable screen and in the | |
196 * scrollback buffer of this terminal. | |
197 * | |
198 * This is a method from the RowProvider interface. The ScrollPort uses | |
199 * it to compute the size of the scrollbar. | |
200 * | |
201 * @return {integer} The number of rows in this terminal. | |
202 */ | |
203 hterm.Terminal.prototype.getRowCount = function() { | |
204 return this.scrollbackRows_.length + this.screen_.rowsArray.length; | |
205 }; | |
206 | |
207 /** | |
208 * Create DOM nodes for new rows and append them to the end of the terminal. | |
209 * | |
210 * This is the only correct way to add a new DOM node for a row. Notice that | |
211 * the new row is appended to the bottom of the list of rows, and does not | |
212 * require renumbering (of the rowIndex property) of previous rows. | |
213 * | |
214 * If you think you want a new blank row somewhere in the middle of the | |
215 * terminal, look into moveRows_(). | |
216 * | |
217 * This method does not pay attention to vtScrollTop/Bottom, since you should | |
218 * be using moveRows() in cases where they would matter. | |
219 * | |
220 * The cursor will be positioned at column 0 of the first inserted line. | |
221 */ | |
222 hterm.Terminal.prototype.appendRows_ = function(count) { | |
223 var cursorRow = this.screen_.rowsArray.length; | |
224 var offset = this.scrollbackRows_.length + cursorRow; | |
225 for (var i = 0; i < count; i++) { | |
226 var row = this.document_.createElement('x-row'); | |
227 row.appendChild(this.document_.createTextNode('')); | |
228 row.rowIndex = offset + i; | |
229 this.screen_.pushRow(row); | |
230 } | |
231 | |
232 var extraRows = this.screen_.rowsArray.length - this.screenSize.height; | |
233 if (extraRows > 0) { | |
234 var ary = this.screen_.shiftRows(extraRows); | |
235 Array.prototype.push.apply(this.scrollbackRows_, ary); | |
236 this.scheduleScrollDown_(); | |
237 } | |
238 | |
239 if (cursorRow >= this.screen_.rowsArray.length) | |
240 cursorRow = this.screen_.rowsArray.length - 1; | |
241 | |
242 this.screen_.setCursorPosition(cursorRow, 0); | |
243 }; | |
244 | |
245 /** | |
246 * Relocate rows from one part of the addressable screen to another. | |
247 * | |
248 * This is used to recycle rows during VT scrolls (those which are driven | |
249 * by VT commands, rather than by the user manipulating the scrollbar.) | |
250 * | |
251 * In this case, the blank lines scrolled into the scroll region are made of | |
252 * the nodes we scrolled off. These have their rowIndex properties carefully | |
253 * renumbered so as not to confuse the ScrollPort. | |
254 * | |
255 * TODO(rginda): I'm not sure why this doesn't require a scrollport repaint. | |
256 * It may just be luck. I wouldn't be surprised if we actually needed to call | |
257 * scrollPort_.invalidateRowRange, but I'm going to wait for evidence before | |
258 * adding it. | |
259 */ | |
260 hterm.Terminal.prototype.moveRows_ = function(fromIndex, count, toIndex) { | |
261 var ary = this.screen_.removeRows(fromIndex, count); | |
262 this.screen_.insertRows(toIndex, ary); | |
263 | |
264 var start, end; | |
265 if (fromIndex < toIndex) { | |
266 start = fromIndex; | |
267 end = fromIndex + count; | |
268 } else { | |
269 start = toIndex; | |
270 end = toIndex + count; | |
271 } | |
272 | |
273 this.renumberRows_(start, end); | |
274 }; | |
275 | |
276 /** | |
277 * Renumber the rowIndex property of the given range of rows. | |
278 * | |
279 * The start and end indicies are relative to the screen, not the scrollback. | |
280 * Rows in the scrollback buffer cannot be renumbered. Since they are not | |
281 * addressable (you cant delete them, scroll them, etc), you should have | |
282 * no need to renumber scrollback rows. | |
283 */ | |
284 hterm.Terminal.prototype.renumberRows_ = function(start, end) { | |
285 var offset = this.scrollbackRows_.length; | |
286 for (var i = start; i < end; i++) { | |
287 this.screen_.rowsArray[i].rowIndex = offset + i; | |
288 } | |
289 }; | |
290 | |
291 /** | |
292 * Print a string to the terminal. | |
293 * | |
294 * This respects the current insert and wraparound modes. It will add new lines | |
295 * to the end of the terminal, scrolling off the top into the scrollback buffer | |
296 * if necessary. | |
297 * | |
298 * The string is *not* parsed for escape codes. Use the interpret() method if | |
299 * that's what you're after. | |
300 * | |
301 * @param{string} str The string to print. | |
302 */ | |
303 hterm.Terminal.prototype.print = function(str) { | |
304 do { | |
305 if (this.options_.insertMode) { | |
306 str = this.screen_.insertString(str); | |
307 } else { | |
308 str = this.screen_.overwriteString(str); | |
309 } | |
310 | |
311 if (this.options_.wraparound && str) { | |
312 this.newLine(); | |
313 } else { | |
314 break; | |
315 } | |
316 } while (str); | |
317 | |
318 this.scheduleSyncCursorPosition_(); | |
319 }; | |
320 | |
321 /** | |
322 * Return the top row index according to the VT. | |
323 * | |
324 * This will return 0 unless the terminal has been told to restrict scrolling | |
325 * to some lower row. It is used for some VT cursor positioning and scrolling | |
326 * commands. | |
327 * | |
328 * @return {integer} The topmost row in the terminal's scroll region. | |
329 */ | |
330 hterm.Terminal.prototype.getVTScrollTop = function() { | |
331 if (this.vtScrollTop_ != null) | |
332 return this.vtScrollTop_; | |
333 | |
334 return 0; | |
335 } | |
336 | |
337 /** | |
338 * Return the bottom row index according to the VT. | |
339 * | |
340 * This will return the height of the terminal unless the it has been told to | |
341 * restrict scrolling to some higher row. It is used for some VT cursor | |
342 * positioning and scrolling commands. | |
343 * | |
344 * @return {integer} The bottommost row in the terminal's scroll region. | |
345 */ | |
346 hterm.Terminal.prototype.getVTScrollBottom = function() { | |
347 if (this.vtScrollBottom_ != null) | |
348 return this.vtScrollBottom_; | |
349 | |
350 return this.screenSize.height; | |
351 } | |
352 | |
353 /** | |
354 * Process a '\n' character. | |
355 * | |
356 * If the cursor is on the final row of the terminal this will append a new | |
357 * blank row to the screen and scroll the topmost row into the scrollback | |
358 * buffer. | |
359 * | |
360 * Otherwise, this moves the cursor to column zero of the next row. | |
361 */ | |
362 hterm.Terminal.prototype.newLine = function() { | |
363 if (this.screen_.cursorPosition.row == this.screen_.rowsArray.length - 1) { | |
364 this.appendRows_(1); | |
365 } else { | |
366 this.screen_.setCursorPosition(this.screen_.cursorPosition.row + 1, 0); | |
367 } | |
368 }; | |
369 | |
370 /** | |
371 * Like newLine(), except maintain the cursor column. | |
372 */ | |
373 hterm.Terminal.prototype.lineFeed = function() { | |
374 var column = this.screen_.cursorPosition.column; | |
375 this.newLine(); | |
376 this.setCursorColumn(column); | |
377 }; | |
378 | |
379 /** | |
380 * Replace all characters to the left of the current cursor with the space | |
381 * character. | |
382 * | |
383 * TODO(rginda): This should probably *remove* the characters (not just replace | |
384 * with a space) if there are no characters at or beyond the current cursor | |
385 * position. Once it does that, it'll have the same text-attribute related | |
386 * issues as hterm.Screen.prototype.clearCursorRow :/ | |
387 */ | |
388 hterm.Terminal.prototype.eraseToLeft = function() { | |
389 var currentColumn = this.screen_.cursorPosition.column; | |
390 this.setCursorColumn(0); | |
391 this.screen_.overwriteString(hterm.getWhitespace(currentColumn + 1)); | |
392 this.setCursorColumn(currentColumn); | |
393 }; | |
394 | |
395 /** | |
396 * Erase a given number of characters to the right of the cursor, shifting | |
397 * remaining characters to the left. | |
398 * | |
399 * The cursor position is unchanged. | |
400 * | |
401 * TODO(rginda): Test that this works even when the cursor is positioned beyond | |
402 * the end of the text. | |
403 * | |
404 * TODO(rginda): This likely has text-attribute related troubles similar to the | |
405 * todo on hterm.Screen.prototype.clearCursorRow. | |
406 */ | |
407 hterm.Terminal.prototype.eraseToRight = function(opt_count) { | |
408 var currentColumn = this.screen_.cursorPosition.column; | |
409 | |
410 var maxCount = this.screenSize.width - currentColumn; | |
411 var count = (opt_count && opt_count < maxCount) ? opt_count : maxCount; | |
412 this.screen_.deleteChars(count); | |
413 this.setCursorColumn(currentColumn); | |
414 }; | |
415 | |
416 /** | |
417 * Erase the current line. | |
418 * | |
419 * The cursor position is unchanged. | |
420 * | |
421 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which | |
422 * has a text-attribute related TODO. | |
423 */ | |
424 hterm.Terminal.prototype.eraseLine = function() { | |
425 var currentColumn = this.screen_.cursorPosition.column; | |
426 this.screen_.clearCursorRow(); | |
427 this.setCursorColumn(currentColumn); | |
428 }; | |
429 | |
430 /** | |
431 * Erase all characters from the start of the scroll region to the current | |
432 * cursor position. | |
433 * | |
434 * The cursor position is unchanged. | |
435 * | |
436 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which | |
437 * has a text-attribute related TODO. | |
438 */ | |
439 hterm.Terminal.prototype.eraseAbove = function() { | |
440 var currentRow = this.screen_.cursorPosition.row; | |
441 var currentColumn = this.screen_.cursorPosition.column; | |
442 | |
443 var top = this.getVTScrollTop(); | |
444 for (var i = top; i < currentRow; i++) { | |
445 this.screen_.setCursorPosition(i, 0); | |
446 this.screen_.clearCursorRow(); | |
447 } | |
448 | |
449 this.screen_.setCursorPosition(currentRow, currentColumn); | |
450 }; | |
451 | |
452 /** | |
453 * Erase all characters from the current cursor position to the end of the | |
454 * scroll region. | |
455 * | |
456 * The cursor position is unchanged. | |
457 * | |
458 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which | |
459 * has a text-attribute related TODO. | |
460 */ | |
461 hterm.Terminal.prototype.eraseBelow = function() { | |
462 var currentRow = this.screen_.cursorPosition.row; | |
463 var currentColumn = this.screen_.cursorPosition.column; | |
464 | |
465 var bottom = this.getVTScrollBottom(); | |
466 for (var i = currentRow + 1; i < bottom; i++) { | |
467 this.screen_.setCursorPosition(i, 0); | |
468 this.screen_.clearCursorRow(); | |
469 } | |
470 | |
471 this.screen_.setCursorPosition(currentRow, currentColumn); | |
472 }; | |
473 | |
474 /** | |
475 * Erase the entire scroll region. | |
476 * | |
477 * The cursor position is unchanged. | |
478 * | |
479 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which | |
480 * has a text-attribute related TODO. | |
481 */ | |
482 hterm.Terminal.prototype.clear = function() { | |
483 var currentRow = this.screen_.cursorPosition.row; | |
484 var currentColumn = this.screen_.cursorPosition.column; | |
485 | |
486 var top = this.getVTScrollTop(); | |
487 var bottom = this.getVTScrollBottom(); | |
488 | |
489 for (var i = top; i < bottom; i++) { | |
490 this.screen_.setCursorPosition(i, 0); | |
491 this.screen_.clearCursorRow(); | |
492 } | |
493 | |
494 this.screen_.setCursorPosition(currentRow, currentColumn); | |
495 }; | |
496 | |
497 /** | |
498 * VT command to insert lines at the current cursor row. | |
499 * | |
500 * This respects the current scroll region. Rows pushed off the bottom are | |
501 * lost (they won't show up in the scrollback buffer). | |
502 * | |
503 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which | |
504 * has a text-attribute related TODO. | |
505 * | |
506 * @param {integer} count The number of lines to insert. | |
507 */ | |
508 hterm.Terminal.prototype.insertLines = function(count) { | |
509 var currentRow = this.screen_.cursorPosition.row; | |
510 | |
511 var bottom = this.getVTScrollBottom(); | |
512 count = Math.min(count, bottom - currentRow); | |
513 | |
514 var start = bottom - count; | |
515 if (start != currentRow) | |
516 this.moveRows_(start, count, currentRow); | |
517 | |
518 for (var i = 0; i < count; i++) { | |
519 this.screen_.setCursorPosition(currentRow + i, 0); | |
520 this.screen_.clearCursorRow(); | |
521 } | |
522 | |
523 this.screen_.setCursorPosition(currentRow, 0); | |
524 }; | |
525 | |
526 /** | |
527 * VT command to delete lines at the current cursor row. | |
528 * | |
529 * New rows are added to the bottom of scroll region to take their place. New | |
530 * rows are strictly there to take up space and have no content or style. | |
531 */ | |
532 hterm.Terminal.prototype.deleteLines = function(count) { | |
533 var currentRow = this.screen_.cursorPosition.row; | |
534 var currentColumn = this.screen_.cursorPosition.column; | |
535 | |
536 var top = currentRow; | |
537 var bottom = this.getVTScrollBottom(); | |
538 | |
539 var maxCount = bottom - top; | |
540 count = Math.min(count, maxCount); | |
541 | |
542 var moveStart = bottom - count; | |
543 if (count != maxCount) | |
544 this.moveRows_(top, count, moveStart); | |
545 | |
546 for (var i = 0; i < count; i++) { | |
547 this.screen_.setCursorPosition(moveStart + i, 0); | |
548 this.screen_.clearCursorRow(); | |
549 } | |
550 | |
551 this.screen_.setCursorPosition(currentRow, currentColumn); | |
552 }; | |
553 | |
554 /** | |
555 * Inserts the given number of spaces at the current cursor position. | |
556 * | |
557 * The cursor is left at the end of the inserted spaces. | |
558 */ | |
559 hterm.Terminal.prototype.insertSpace = function(count) { | |
560 var ws = hterm.getWhitespace(count); | |
561 this.screen_.insertString(ws); | |
562 }; | |
563 | |
564 /** | |
565 * Forward-delete the specified number of characters starting at the cursor | |
566 * position. | |
567 * | |
568 * @param {integer} count The number of characters to delete. | |
569 */ | |
570 hterm.Terminal.prototype.deleteChars = function(count) { | |
571 this.screen_.deleteChars(count); | |
572 }; | |
573 | |
574 /** | |
575 * Shift rows in the scroll region upwards by a given number of lines. | |
576 * | |
577 * New rows are inserted at the bottom of the scroll region to fill the | |
578 * vacated rows. The new rows not filled out with the current text attributes. | |
579 * | |
580 * This function does not affect the scrollback rows at all. Rows shifted | |
581 * off the top are lost. | |
582 * | |
583 * @param {integer} count The number of rows to scroll. | |
584 */ | |
585 hterm.Terminal.prototype.vtScrollUp = function(count) { | |
586 var currentRow = this.screen_.cursorPosition.row; | |
587 var currentColumn = this.screen_.cursorPosition.column; | |
588 | |
589 this.setCursorRow(this.getVTScrollTop()); | |
590 this.deleteLines(count); | |
591 | |
592 this.screen_.setCursorPosition(currentRow, currentColumn); | |
593 }; | |
594 | |
595 /** | |
596 * Shift rows below the cursor down by a given number of lines. | |
597 * | |
598 * This function respects the current scroll region. | |
599 * | |
600 * New rows are inserted at the top of the scroll region to fill the | |
601 * vacated rows. The new rows not filled out with the current text attributes. | |
602 * | |
603 * This function does not affect the scrollback rows at all. Rows shifted | |
604 * off the bottom are lost. | |
605 * | |
606 * @param {integer} count The number of rows to scroll. | |
607 */ | |
608 hterm.Terminal.prototype.vtScrollDown = function(opt_count) { | |
609 var currentRow = this.screen_.cursorPosition.row; | |
610 var currentColumn = this.screen_.cursorPosition.column; | |
611 | |
612 this.setCursorRow(this.getVTScrollTop()); | |
613 this.insertLines(opt_count); | |
614 | |
615 this.screen_.setCursorPosition(currentRow, currentColumn); | |
616 }; | |
617 | |
618 /** | |
619 * Set the cursor position. | |
620 * | |
621 * The cursor row is relative to the scroll region if the terminal has | |
622 * 'origin mode' enabled, or relative to the addressable screen otherwise. | |
623 * | |
624 * @param {integer} row The new zero-based cursor row. | |
625 * @param {integer} row The new zero-based cursor column. | |
626 */ | |
627 hterm.Terminal.prototype.setCursorPosition = function(row, column) { | |
628 if (this.options_.originMode) { | |
629 var scrollTop = this.getScrollTop(); | |
630 row = hterm.clamp(row + scrollTop, scrollTop, this.getScrollBottom()); | |
631 } else { | |
632 row = hterm.clamp(row, 0, this.screenSize.height); | |
633 } | |
634 | |
635 this.screen_.setCursorPosition(row, column); | |
636 }; | |
637 | |
638 /** | |
639 * Set the cursor column. | |
640 * | |
641 * @param {integer} column The new zero-based cursor column. | |
642 */ | |
643 hterm.Terminal.prototype.setCursorColumn = function(column) { | |
644 this.screen_.setCursorPosition(this.screen_.cursorPosition.row, column); | |
645 }; | |
646 | |
647 /** | |
648 * Return the cursor column. | |
649 * | |
650 * @return {integer} The zero-based cursor column. | |
651 */ | |
652 hterm.Terminal.prototype.getCursorColumn = function() { | |
653 return this.screen_.cursorPosition.column; | |
654 }; | |
655 | |
656 /** | |
657 * Set the cursor row. | |
658 * | |
659 * The cursor row is relative to the scroll region if the terminal has | |
660 * 'origin mode' enabled, or relative to the addressable screen otherwise. | |
661 * | |
662 * @param {integer} row The new cursor row. | |
663 */ | |
664 hterm.Terminal.prototype.setCursorRow = function(row) { | |
665 this.setCursorPosition(row, this.screen_.cursorPosition.column); | |
666 }; | |
667 | |
668 /** | |
669 * Return the cursor row. | |
670 * | |
671 * @return {integer} The zero-based cursor row. | |
672 */ | |
673 hterm.Terminal.prototype.getCursorRow = function(row) { | |
674 return this.screen_.cursorPosition.row; | |
675 }; | |
676 | |
677 /** | |
678 * Request that the ScrollPort redraw itself soon. | |
679 * | |
680 * The redraw will happen asynchronously, soon after the call stack winds down. | |
681 * Multiple calls will be coalesced into a single redraw. | |
682 */ | |
683 hterm.Terminal.prototype.scheduleRedraw_ = function() { | |
684 if (this.redrawTimeout_) | |
685 clearTimeout(this.redrawTimeout_); | |
686 | |
687 var self = this; | |
688 setTimeout(function() { | |
689 self.redrawTimeout_ = null; | |
690 self.scrollPort_.redraw_(); | |
691 }, 0); | |
692 }; | |
693 | |
694 /** | |
695 * Request that the ScrollPort be scrolled to the bottom. | |
696 * | |
697 * The scroll will happen asynchronously, soon after the call stack winds down. | |
698 * Multiple calls will be coalesced into a single scroll. | |
699 * | |
700 * This affects the scrollbar position of the ScrollPort, and has nothing to | |
701 * do with the VT scroll commands. | |
702 */ | |
703 hterm.Terminal.prototype.scheduleScrollDown_ = function() { | |
704 var f = this.scheduleScrollDown_; | |
dgozman
2011/11/30 07:57:01
If I'm not mistaken, |f| will be the same object f
| |
705 | |
706 if (f.timeout) | |
707 clearTimeout(f.timeout); | |
708 | |
709 var self = this; | |
710 f.timeout = setTimeout(function() { | |
711 f.timeout = null; | |
712 self.scrollPort_.scrollRowToBottom(self.getRowCount()); | |
713 }, 10); | |
714 }; | |
715 | |
716 /** | |
717 * Move the cursor up a specified number of rows. | |
718 * | |
719 * @param {integer} count The number of rows to move the cursor. | |
720 */ | |
721 hterm.Terminal.prototype.cursorUp = function(count) { | |
722 return this.cursorDown(-count); | |
723 }; | |
724 | |
725 /** | |
726 * Move the cursor down a specified number of rows. | |
727 * | |
728 * @param {integer} count The number of rows to move the cursor. | |
729 */ | |
730 hterm.Terminal.prototype.cursorDown = function(count) { | |
731 var minHeight = (this.options_.originMode ? this.getVTScrollTop() : 0); | |
732 var maxHeight = (this.options_.originMode ? this.getVTScrollBottom() : | |
733 this.screenSize.height - 1); | |
734 | |
735 var row = hterm.clamp(this.screen_.cursorPosition.row + count, | |
736 minHeight, maxHeight); | |
737 this.setCursorRow(row); | |
738 }; | |
739 | |
740 /** | |
741 * Move the cursor left a specified number of columns. | |
742 * | |
743 * @param {integer} count The number of columns to move the cursor. | |
744 */ | |
745 hterm.Terminal.prototype.cursorLeft = function(count) { | |
746 return this.cursorRight(-count); | |
747 }; | |
748 | |
749 /** | |
750 * Move the cursor right a specified number of columns. | |
751 * | |
752 * @param {integer} count The number of columns to move the cursor. | |
753 */ | |
754 hterm.Terminal.prototype.cursorRight = function(count) { | |
755 var column = hterm.clamp(this.screen_.cursorPosition.column + count, | |
756 0, this.screenSize.width); | |
757 this.setCursorColumn(column); | |
758 }; | |
759 | |
760 /** | |
761 * Reverse the foreground and background colors of the terminal. | |
762 * | |
763 * This only affects text that was drawn with no attributes. | |
764 * | |
765 * TODO(rginda): Test xterm to see if reverse is respected for text that has | |
766 * been drawn with attributes that happen to coincide with the default | |
767 * 'no-attribute' colors. My guess is probably not. | |
768 */ | |
769 hterm.Terminal.prototype.setReverseVideo = function(state) { | |
770 if (state) { | |
771 this.scrollPort_.setForegroundColor(this.backgroundColor); | |
772 this.scrollPort_.setBackgroundColor(this.foregroundColor); | |
773 } else { | |
774 this.scrollPort_.setForegroundColor(this.foregroundColor); | |
775 this.scrollPort_.setBackgroundColor(this.backgroundColor); | |
776 } | |
777 }; | |
778 | |
779 /** | |
780 * Set the origin mode bit. | |
781 * | |
782 * If origin mode is on, certain VT cursor and scrolling commands measure their | |
783 * row parameter relative to the VT scroll region. Otherwise, row 0 corresponds | |
784 * to the top of the addressable screen. | |
785 * | |
786 * Defaults to off. | |
787 * | |
788 * @param {boolean} state True to set origin mode, false to unset. | |
789 */ | |
790 hterm.Terminal.prototype.setOriginMode = function(state) { | |
791 this.options_.originMode = state; | |
792 }; | |
793 | |
794 /** | |
795 * Set the insert mode bit. | |
796 * | |
797 * If insert mode is on, existing text beyond the cursor position will be | |
798 * shifted right to make room for new text. Otherwise, new text overwrites | |
799 * any existing text. | |
800 * | |
801 * Defaults to off. | |
802 * | |
803 * @param {boolean} state True to set insert mode, false to unset. | |
804 */ | |
805 hterm.Terminal.prototype.setInsertMode = function(state) { | |
806 this.options_.insertMode = state; | |
807 }; | |
808 | |
809 /** | |
810 * Set the wraparound mode bit. | |
811 * | |
812 * If wraparound mode is on, certain VT commands will allow the cursor to wrap | |
813 * to the start of the following row. Otherwise, the cursor is clamped to the | |
814 * end of the screen and attempts to write past it are ignored. | |
815 * | |
816 * Defaults to on. | |
817 * | |
818 * @param {boolean} state True to set wraparound mode, false to unset. | |
819 */ | |
820 hterm.Terminal.prototype.setWraparound = function(state) { | |
821 this.options_.wraparound = state; | |
822 }; | |
823 | |
824 /** | |
825 * Set the reverse-wraparound mode bit. | |
826 * | |
827 * If wraparound mode is off, certain VT commands will allow the cursor to wrap | |
828 * to the end of the previous row. Otherwise, the cursor is clamped to column | |
829 * 0. | |
830 * | |
831 * Defaults to off. | |
832 * | |
833 * @param {boolean} state True to set reverse-wraparound mode, false to unset. | |
834 */ | |
835 hterm.Terminal.prototype.setReverseWraparound = function(state) { | |
836 this.options_.reverseWraparound = state; | |
837 }; | |
838 | |
839 /** | |
840 * Selects between the primary and alternate screens. | |
841 * | |
842 * If alternate mode is on, the alternate screen is active. Otherwise the | |
843 * primary screen is active. | |
844 * | |
845 * Swapping screens has no effect on the scrollback buffer. | |
846 * | |
847 * Each screen maintains its own cursor position. | |
848 * | |
849 * Defaults to off. | |
850 * | |
851 * @param {boolean} state True to set alternate mode, false to unset. | |
852 */ | |
853 hterm.Terminal.prototype.setAlternateMode = function(state) { | |
854 this.screen_ = state ? this.alternateScreen_ : this.primaryScreen_; | |
855 | |
856 this.screen_.setColumnCount(this.screenSize.width); | |
857 | |
858 var rowDelta = this.screenSize.height - this.screen_.getHeight(); | |
859 if (rowDelta > 0) | |
860 this.appendRows_(rowDelta); | |
861 | |
862 this.scrollPort_.invalidateRowRange( | |
863 this.scrollbackRows_.length, | |
864 this.scrollbackRows_.length + this.screenSize.height); | |
865 | |
866 if (this.screen_.cursorPosition.row == -1) | |
867 this.screen_.setCursorPosition(0, 0); | |
868 | |
869 this.syncCursorPosition_(); | |
870 }; | |
871 | |
872 /** | |
873 * Set the cursor-blink mode bit. | |
874 * | |
875 * If cursor-blink is on, the cursor will blink when it is visible. Otherwise | |
876 * a visible cursor does not blink. | |
877 * | |
878 * You should make sure to turn blinking off if you're going to dispose of a | |
879 * terminal, otherwise you'll leak a timeout. | |
880 * | |
881 * Defaults to on. | |
882 * | |
883 * @param {boolean} state True to set cursor-blink mode, false to unset. | |
884 */ | |
885 hterm.Terminal.prototype.setCursorBlink = function(state) { | |
886 this.options_.cursorBlink = state; | |
887 | |
888 if (!state && this.cursorTimer_) { | |
889 clearTimeout(this.cursorTimer_); | |
890 this.cursorTimer_ = null; | |
891 } | |
892 | |
893 if (this.options_.cursorVisible) | |
894 this.setCursorVisible(true); | |
895 }; | |
896 | |
897 /** | |
898 * Set the cursor-visible mode bit. | |
899 * | |
900 * If cursor-visible is on, the cursor will be visible. Otherwise it will not. | |
901 * | |
902 * Defaults to on. | |
903 * | |
904 * @param {boolean} state True to set cursor-visible mode, false to unset. | |
905 */ | |
906 hterm.Terminal.prototype.setCursorVisible = function(state) { | |
907 this.options_.cursorVisible = state; | |
908 | |
909 if (!state) { | |
910 this.cursorNode_.style.display = 'none'; | |
911 return; | |
912 } | |
913 | |
914 this.cursorNode_.style.display = 'block'; | |
915 | |
916 if (this.options_.cursorBlink) { | |
917 if (this.cursorTimer_) | |
918 return; | |
919 | |
920 this.cursorTimer_ = setInterval(this.onCursorBlink_.bind(this), 500); | |
921 } else { | |
922 if (!this.cursorTimer_) | |
923 return; | |
924 | |
925 clearTimeout(this.cursorTimer_); | |
926 this.cursorTimer_ = null; | |
927 } | |
928 }; | |
929 | |
930 /** | |
931 * Synchronizes the visible cursor with the current cursor coordinates. | |
932 */ | |
933 hterm.Terminal.prototype.syncCursorPosition_ = function() { | |
934 var topRowIndex = this.scrollPort_.getTopRowIndex(); | |
935 var bottomRowIndex = this.scrollPort_.getBottomRowIndex(topRowIndex); | |
936 var cursorRowIndex = this.scrollbackRows_.length + | |
937 this.screen_.cursorPosition.row; | |
938 | |
939 if (cursorRowIndex > bottomRowIndex) { | |
940 // Cursor is scrolled off screen, move it outside of the visible area. | |
941 this.cursorNode_.style.top = -this.characterSize_.height; | |
942 return; | |
943 } | |
944 | |
945 this.cursorNode_.style.top = this.scrollPort_.visibleRowTopMargin + | |
946 this.characterSize_.height * (cursorRowIndex - topRowIndex); | |
947 this.cursorNode_.style.left = this.characterSize_.width * | |
948 this.screen_.cursorPosition.column; | |
949 }; | |
950 | |
951 /** | |
952 * Synchronizes the visible cursor with the current cursor coordinates. | |
953 * | |
954 * The sync will happen asynchronously, soon after the call stack winds down. | |
955 * Multiple calls will be coalesced into a single sync. | |
956 */ | |
957 hterm.Terminal.prototype.scheduleSyncCursorPosition_ = function() { | |
958 var f = this.scheduleSyncCursorPosition_; | |
959 | |
960 if (f.timeout) | |
961 clearTimeout(f.timeout); | |
962 | |
963 var self = this; | |
964 setTimeout(function() { | |
965 self.syncCursorPosition_(); | |
966 f.timeout = null; | |
967 }, 100); | |
968 }; | |
969 | |
970 /** | |
971 * React when the ScrollPort is scrolled. | |
972 */ | |
973 hterm.Terminal.prototype.onScroll_ = function() { | |
974 this.scheduleSyncCursorPosition_(); | |
975 }; | |
976 | |
977 /** | |
978 * React when the ScrollPort is resized. | |
979 */ | |
980 hterm.Terminal.prototype.onResize_ = function() { | |
981 var width = Math.floor(this.scrollPort_.getScreenWidth() / | |
982 this.characterSize_.width); | |
983 var height = this.scrollPort_.visibleRowCount; | |
984 | |
985 if (width == this.screenSize.width && height == this.screenSize.height) | |
986 return; | |
987 | |
988 this.screenSize.resize(width, height); | |
989 | |
990 var screenHeight = this.screen_.getHeight(); | |
991 | |
992 var deltaRows = this.screenSize.height - screenHeight; | |
993 | |
994 if (deltaRows < 0) { | |
995 // Screen got smaller. | |
996 var ary = this.screen_.shiftRows(-deltaRows); | |
997 this.scrollbackRows_.push.apply(this.scrollbackRows_, ary); | |
998 } else if (deltaRows > 0) { | |
999 // Screen got larger. | |
1000 | |
1001 if (deltaRows <= this.scrollbackRows_.length) { | |
1002 var scrollbackCount = Math.min(deltaRows, this.scrollbackRows_.length); | |
1003 var rows = this.scrollbackRows_.splice( | |
1004 0, this.scrollbackRows_.length - scrollbackCount); | |
1005 this.screen_.unshiftRows(rows); | |
1006 deltaRows -= scrollbackCount; | |
1007 } | |
1008 | |
1009 if (deltaRows) | |
1010 this.appendRows_(deltaRows); | |
1011 } | |
1012 | |
1013 this.screen_.setColumnCount(this.screenSize.width); | |
1014 | |
1015 if (this.screen_.cursorPosition.row == -1) | |
1016 this.screen_.setCursorPosition(0, 0); | |
1017 }; | |
1018 | |
1019 /** | |
1020 * Service the cursor blink timeout. | |
1021 */ | |
1022 hterm.Terminal.prototype.onCursorBlink_ = function() { | |
1023 if (this.cursorNode_.style.display == 'block') { | |
1024 this.cursorNode_.style.display = 'none'; | |
1025 } else { | |
1026 this.cursorNode_.style.display = 'block'; | |
1027 } | |
1028 }; | |
OLD | NEW |