blob: e6da05c53880d8d9173bdda2dd65bbac033855f6 [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
7lib.rtdep('lib.colors', 'lib.PreferenceManager',
8 'hterm.msg',
9 'hterm.Keyboard', 'hterm.Options', 'hterm.Screen',
10 'hterm.ScrollPort', 'hterm.Size', 'hterm.VT');
11
rginda8ba33642011-12-14 12:31:31 -080012/**
13 * Constructor for the Terminal class.
14 *
15 * A Terminal pulls together the hterm.ScrollPort, hterm.Screen and hterm.VT100
16 * classes to provide the complete terminal functionality.
17 *
18 * There are a number of lower-level Terminal methods that can be called
19 * directly to manipulate the cursor, text, scroll region, and other terminal
20 * attributes. However, the primary method is interpret(), which parses VT
21 * escape sequences and invokes the appropriate Terminal methods.
22 *
23 * This class was heavily influenced by Cory Maccarrone's Framebuffer class.
24 *
25 * TODO(rginda): Eventually we're going to need to support characters which are
26 * displayed twice as wide as standard latin characters. This is to support
27 * CJK (and possibly other character sets).
rginda9f5222b2012-03-05 11:53:28 -080028 *
29 * @param {string} opt_profileName Optional preference profile name. If not
30 * provided, defaults to 'default'.
rginda8ba33642011-12-14 12:31:31 -080031 */
rginda9f5222b2012-03-05 11:53:28 -080032hterm.Terminal = function(opt_profileName) {
33 this.profileName_ = null;
34 this.setProfile(opt_profileName || 'default');
35
rginda8ba33642011-12-14 12:31:31 -080036 // Two screen instances.
37 this.primaryScreen_ = new hterm.Screen();
38 this.alternateScreen_ = new hterm.Screen();
39
40 // The "current" screen.
41 this.screen_ = this.primaryScreen_;
42
rginda8ba33642011-12-14 12:31:31 -080043 // The local notion of the screen size. ScreenBuffers also have a size which
44 // indicates their present size. During size changes, the two may disagree.
45 // Also, the inactive screen's size is not altered until it is made the active
46 // screen.
47 this.screenSize = new hterm.Size(0, 0);
48
rginda8ba33642011-12-14 12:31:31 -080049 // The scroll port we'll be using to display the visible rows.
rginda35c456b2012-02-09 17:29:05 -080050 this.scrollPort_ = new hterm.ScrollPort(this);
rginda8ba33642011-12-14 12:31:31 -080051 this.scrollPort_.subscribe('resize', this.onResize_.bind(this));
52 this.scrollPort_.subscribe('scroll', this.onScroll_.bind(this));
rginda9846e2f2012-01-27 13:53:33 -080053 this.scrollPort_.subscribe('paste', this.onPaste_.bind(this));
rginda8ba33642011-12-14 12:31:31 -080054
rginda87b86462011-12-14 13:48:03 -080055 // The div that contains this terminal.
56 this.div_ = null;
57
rgindac9bc5502012-01-18 11:48:44 -080058 // The document that contains the scrollPort. Defaulted to the global
59 // document here so that the terminal is functional even if it hasn't been
60 // inserted into a document yet, but re-set in decorate().
61 this.document_ = window.document;
rginda87b86462011-12-14 13:48:03 -080062
rginda8ba33642011-12-14 12:31:31 -080063 // The rows that have scrolled off screen and are no longer addressable.
64 this.scrollbackRows_ = [];
65
rgindac9bc5502012-01-18 11:48:44 -080066 // Saved tab stops.
67 this.tabStops_ = [];
68
David Benjamin66e954d2012-05-05 21:08:12 -040069 // Keep track of whether default tab stops have been erased; after a TBC
70 // clears all tab stops, defaults aren't restored on resize until a reset.
71 this.defaultTabStops = true;
72
rginda8ba33642011-12-14 12:31:31 -080073 // The VT's notion of the top and bottom rows. Used during some VT
74 // cursor positioning and scrolling commands.
75 this.vtScrollTop_ = null;
76 this.vtScrollBottom_ = null;
77
78 // The DIV element for the visible cursor.
79 this.cursorNode_ = null;
80
rginda9f5222b2012-03-05 11:53:28 -080081 // These prefs are cached so we don't have to read from local storage with
82 // each output and keystroke.
83 this.scrollOnOutput_ = this.prefs_.get('scroll-on-output');
84 this.scrollOnKeystroke_ = this.prefs_.get('scroll-on-keystroke');
rginda8e92a692012-05-20 19:37:20 -070085 this.foregroundColor_ = this.prefs_.get('foreground-color');
86 this.backgroundColor_ = this.prefs_.get('background-color');
rginda9f5222b2012-03-05 11:53:28 -080087
rgindaf0090c92012-02-10 14:58:52 -080088 // Terminal bell sound.
89 this.bellAudio_ = this.document_.createElement('audio');
rginda9f5222b2012-03-05 11:53:28 -080090 this.bellAudio_.setAttribute('src', this.prefs_.get('audible-bell-sound'));
rgindaf0090c92012-02-10 14:58:52 -080091 this.bellAudio_.setAttribute('preload', 'auto');
92
rginda6d397402012-01-17 10:58:29 -080093 // Cursor position and attributes saved with DECSC.
94 this.savedOptions_ = {};
95
rginda8ba33642011-12-14 12:31:31 -080096 // The current mode bits for the terminal.
97 this.options_ = new hterm.Options();
98
99 // Timeouts we might need to clear.
100 this.timeouts_ = {};
rginda87b86462011-12-14 13:48:03 -0800101
102 // The VT escape sequence interpreter.
rginda0f5c0292012-01-13 11:00:13 -0800103 this.vt = new hterm.VT(this);
rginda11057d52012-04-25 12:29:56 -0700104 this.vt.enable8BitControl = this.prefs_.get('enable-8-bit-control');
105 this.vt.maxStringSequence = this.prefs_.get('max-string-sequence');
rginda87b86462011-12-14 13:48:03 -0800106
rgindafeaf3142012-01-31 15:14:20 -0800107 // The keyboard hander.
108 this.keyboard = new hterm.Keyboard(this);
109
rginda87b86462011-12-14 13:48:03 -0800110 // General IO interface that can be given to third parties without exposing
111 // the entire terminal object.
112 this.io = new hterm.Terminal.IO(this);
rgindac9bc5502012-01-18 11:48:44 -0800113
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +0400114 this.realizeSize_(80, 24);
rgindac9bc5502012-01-18 11:48:44 -0800115 this.setDefaultTabStops();
rginda87b86462011-12-14 13:48:03 -0800116};
117
118/**
rginda35c456b2012-02-09 17:29:05 -0800119 * Default tab with of 8 to match xterm.
120 */
121hterm.Terminal.prototype.tabWidth = 8;
122
123/**
rginda35c456b2012-02-09 17:29:05 -0800124 * The assumed width of a scrollbar.
125 */
126hterm.Terminal.prototype.scrollbarWidthPx = 16;
127
128/**
rginda9f5222b2012-03-05 11:53:28 -0800129 * Select a preference profile.
130 *
131 * This will load the terminal preferences for the given profile name and
132 * associate subsequent preference changes with the new preference profile.
133 *
134 * @param {string} newName The name of the preference profile. Forward slash
135 * characters will be removed from the name.
136 */
137hterm.Terminal.prototype.setProfile = function(profileName) {
138 // If we already have a profile selected, we're going to need to re-sync
139 // with the new profile.
140 var needSync = !!this.profileName_;
141
142 this.profileName_ = profileName.replace(/\//g, '');
143
rgindacbbd7482012-06-13 15:06:16 -0700144 this.prefs_ = new lib.PreferenceManager(
rginda9f5222b2012-03-05 11:53:28 -0800145 '/hterm/prefs/profiles/' + this.profileName_);
146
147 var self = this;
148 this.prefs_.definePreferences
rginda30f20f62012-04-05 16:36:19 -0700149 ([
150 /**
151 * Set whether the alt key acts as a meta key or as a distinct alt key.
rginda9f5222b2012-03-05 11:53:28 -0800152 */
rginda30f20f62012-04-05 16:36:19 -0700153 ['alt-is-meta', false, function(v) {
rgindaf9c36852012-05-09 11:08:39 -0700154 self.keyboard.altIsMeta = v;
rginda9f5222b2012-03-05 11:53:28 -0800155 }
156 ],
157
rginda30f20f62012-04-05 16:36:19 -0700158 /**
rginda39bdf6f2012-04-10 16:50:55 -0700159 * Controls how the alt key is handled.
160 *
161 * escape....... Send an ESC prefix.
162 * 8-bit........ Add 128 to the unshifted character as in xterm.
163 * browser-key.. Wait for the keypress event and see what the browser says.
164 * (This won't work well on platforms where the browser
165 * performs a default action for some alt sequences.)
rginda30f20f62012-04-05 16:36:19 -0700166 */
rginda39bdf6f2012-04-10 16:50:55 -0700167 ['alt-sends-what', 'escape', function(v) {
168 if (!/^(escape|8-bit|browser-key)$/.test(v))
169 v = 'escape';
170
rgindaf9c36852012-05-09 11:08:39 -0700171 self.keyboard.altSendsWhat = v;
rginda30f20f62012-04-05 16:36:19 -0700172 }
173 ],
174
175 /**
176 * Terminal bell sound. Empty string for no audible bell.
177 */
178 ['audible-bell-sound', '../audio/bell.ogg', function(v) {
179 self.bellAudio_.setAttribute('src', v);
180 }
181 ],
182
183 /**
184 * The background color for text with no other color attributes.
185 */
186 ['background-color', 'rgb(16, 16, 16)', function(v) {
rginda8e92a692012-05-20 19:37:20 -0700187 self.setBackgroundColor(v);
rginda9f5222b2012-03-05 11:53:28 -0800188 }
189 ],
190
191 /**
rginda30f20f62012-04-05 16:36:19 -0700192 * The background image.
rginda30f20f62012-04-05 16:36:19 -0700193 */
rginda8e92a692012-05-20 19:37:20 -0700194 ['background-image', '',
rginda30f20f62012-04-05 16:36:19 -0700195 function(v) {
196 self.scrollPort_.setBackgroundImage(v);
197 }
198 ],
199
200 /**
Philip Douglass959b49d2012-05-30 13:29:29 -0400201 * The background image size,
202 *
203 * Defaults to none.
204 */
205 ['background-size', '', function(v) {
206 self.scrollPort_.setBackgroundSize(v);
207 }
208 ],
209
210 /**
211 * The background image position,
212 *
213 * Defaults to none.
214 */
215 ['background-position', '', function(v) {
216 self.scrollPort_.setBackgroundPosition(v);
217 }
218 ],
219
220 /**
rginda30f20f62012-04-05 16:36:19 -0700221 * If true, the backspace should send BS ('\x08', aka ^H). Otherwise
222 * the backspace key should send '\x7f'.
223 */
224 ['backspace-sends-backspace', false, function(v) {
225 self.keyboard.backspaceSendsBackspace = v;
226 }
227 ],
228
229 /**
rgindade84e382012-04-20 15:39:31 -0700230 * Whether or not to blink the cursor by default.
231 */
232 ['cursor-blink', false, function(v) {
233 self.setCursorBlink(!!v);
234 }
235 ],
236
237 /**
rginda30f20f62012-04-05 16:36:19 -0700238 * The color of the visible cursor.
239 */
240 ['cursor-color', 'rgba(255,0,0,0.5)', function(v) {
rginda8e92a692012-05-20 19:37:20 -0700241 self.setCursorColor(v);
rginda30f20f62012-04-05 16:36:19 -0700242 }
243 ],
244
245 /**
rginda11057d52012-04-25 12:29:56 -0700246 * True to enable 8-bit control characters, false to ignore them.
247 *
248 * We'll respect the two-byte versions of these control characters
249 * regardless of this setting.
250 */
251 ['enable-8-bit-control', false, function(v) {
252 self.vt.enable8BitControl = !!v;
253 }
254 ],
255
256 /**
rginda30f20f62012-04-05 16:36:19 -0700257 * True if we should use bold weight font for text with the bold/bright
258 * attribute. False to use bright colors only. Null to autodetect.
259 */
260 ['enable-bold', null, function(v) {
261 self.syncBoldSafeState();
262 }
263 ],
264
265 /**
rginda9f5222b2012-03-05 11:53:28 -0800266 * Default font family for the terminal text.
267 */
268 ['font-family', ('"DejaVu Sans Mono", "Everson Mono", ' +
269 'FreeMono, "Menlo", "Lucida Console", ' +
270 'monospace'),
271 function(v) { self.syncFontFamily() }
272 ],
273
274 /**
rginda30f20f62012-04-05 16:36:19 -0700275 * The default font size in pixels.
276 */
277 ['font-size', 15, function(v) {
278 self.setFontSize(v);
279 }
280 ],
281
282 /**
rginda9f5222b2012-03-05 11:53:28 -0800283 * Anti-aliasing.
284 */
285 ['font-smoothing', 'antialiased',
286 function(v) { self.syncFontFamily() }
287 ],
288
289 /**
rginda30f20f62012-04-05 16:36:19 -0700290 * The foreground color for text with no other color attributes.
rginda9f5222b2012-03-05 11:53:28 -0800291 */
rginda30f20f62012-04-05 16:36:19 -0700292 ['foreground-color', 'rgb(240, 240, 240)', function(v) {
rginda8e92a692012-05-20 19:37:20 -0700293 self.setForegroundColor(v);
rginda9f5222b2012-03-05 11:53:28 -0800294 }
295 ],
296
297 /**
rginda30f20f62012-04-05 16:36:19 -0700298 * If true, home/end will control the terminal scrollbar and shift home/end
299 * will send the VT keycodes. If false then home/end sends VT codes and
300 * shift home/end scrolls.
rginda9f5222b2012-03-05 11:53:28 -0800301 */
rginda30f20f62012-04-05 16:36:19 -0700302 ['home-keys-scroll', false, function(v) {
303 self.keyboard.homeKeysScroll = v;
304 }
305 ],
306
307 /**
rginda11057d52012-04-25 12:29:56 -0700308 * Max length of a DCS, OSC, PM, or APS sequence before we give up and
309 * ignore the code.
310 */
311 ['max-string-sequence', 1024, function(v) {
312 self.vt.maxStringSequence = v;
313 }
314 ],
315
316 /**
rginda30f20f62012-04-05 16:36:19 -0700317 * Set whether the meta key sends a leading escape or not.
318 */
319 ['meta-sends-escape', true, function(v) {
320 self.keyboard.metaSendsEscape = v;
rginda9f5222b2012-03-05 11:53:28 -0800321 }
322 ],
323
324 /**
325 * If true, scroll to the bottom on any keystroke.
326 */
327 ['scroll-on-keystroke', true, function(v) {
328 self.scrollOnKeystroke_ = v;
329 }
330 ],
331
332 /**
333 * If true, scroll to the bottom on terminal output.
334 */
335 ['scroll-on-output', false, function(v) {
336 self.scrollOnOutput_ = v;
337 }
338 ],
339
340 /**
David Reveman8f552492012-03-28 12:18:41 -0400341 * The vertical scrollbar mode.
342 */
343 ['scrollbar-visible', true, function(v) {
344 self.setScrollbarVisible(v);
345 }
346 ],
rginda30f20f62012-04-05 16:36:19 -0700347
348 /**
rgindaf522ce02012-04-17 17:49:17 -0700349 * The default environment variables.
350 */
351 ['environment', {TERM: 'xterm-256color'}, null],
352
353 /**
rginda30f20f62012-04-05 16:36:19 -0700354 * If true, page up/down will control the terminal scrollbar and shift
355 * page up/down will send the VT keycodes. If false then page up/down
356 * sends VT codes and shift page up/down scrolls.
357 */
358 ['page-keys-scroll', false, function(v) {
359 self.keyboard.pageKeysScroll = v;
360 }
361 ],
362
rginda9f5222b2012-03-05 11:53:28 -0800363 ]);
364
365 if (needSync)
366 this.prefs_.notifyAll();
367};
368
rginda8e92a692012-05-20 19:37:20 -0700369
370/**
371 * Set the color for the cursor.
372 *
373 * If you want this setting to persist, set it through prefs_, rather than
374 * with this method.
375 */
376hterm.Terminal.prototype.setCursorColor = function(color) {
377 this.cursorNode_.style.backgroundColor = color;
378 this.cursorNode_.style.borderColor = color;
379};
380
381/**
382 * Return the current cursor color as a string.
383 */
384hterm.Terminal.prototype.getCursorColor = function() {
385 return this.cursorNode_.style.backgroundColor;
386};
387
388/**
389 * Set the background color.
390 *
391 * If you want this setting to persist, set it through prefs_, rather than
392 * with this method.
393 */
394hterm.Terminal.prototype.setBackgroundColor = function(color) {
rgindacbbd7482012-06-13 15:06:16 -0700395 this.backgroundColor_ = lib.colors.normalizeCSS(color);
rginda8e92a692012-05-20 19:37:20 -0700396 this.scrollPort_.setBackgroundColor(color);
397};
398
rginda9f5222b2012-03-05 11:53:28 -0800399/**
400 * Return the current terminal background color.
401 *
402 * Intended for use by other classes, so we don't have to expose the entire
403 * prefs_ object.
404 */
405hterm.Terminal.prototype.getBackgroundColor = function() {
rginda8e92a692012-05-20 19:37:20 -0700406 return this.backgroundColor_;
407};
408
409/**
410 * Set the foreground color.
411 *
412 * If you want this setting to persist, set it through prefs_, rather than
413 * with this method.
414 */
415hterm.Terminal.prototype.setForegroundColor = function(color) {
rgindacbbd7482012-06-13 15:06:16 -0700416 this.foregroundColor_ = lib.colors.normalizeCSS(color);
rginda8e92a692012-05-20 19:37:20 -0700417 this.scrollPort_.setForegroundColor(color);
rginda9f5222b2012-03-05 11:53:28 -0800418};
419
420/**
421 * Return the current terminal foreground color.
422 *
423 * Intended for use by other classes, so we don't have to expose the entire
424 * prefs_ object.
425 */
426hterm.Terminal.prototype.getForegroundColor = function() {
rginda8e92a692012-05-20 19:37:20 -0700427 return this.foregroundColor_;
rginda9f5222b2012-03-05 11:53:28 -0800428};
429
430/**
rginda87b86462011-12-14 13:48:03 -0800431 * Create a new instance of a terminal command and run it with a given
432 * argument string.
433 *
434 * @param {function} commandClass The constructor for a terminal command.
435 * @param {string} argString The argument string to pass to the command.
436 */
437hterm.Terminal.prototype.runCommandClass = function(commandClass, argString) {
rgindaf522ce02012-04-17 17:49:17 -0700438 var environment = this.prefs_.get('environment');
439 if (typeof environment != 'object' || environment == null)
440 environment = {};
441
rginda87b86462011-12-14 13:48:03 -0800442 var self = this;
443 this.command = new commandClass(
444 { argString: argString || '',
445 io: this.io.push(),
rgindaf522ce02012-04-17 17:49:17 -0700446 environment: environment,
rginda87b86462011-12-14 13:48:03 -0800447 onExit: function(code) {
448 self.io.pop();
449 self.io.println(hterm.msg('COMMAND_COMPLETE',
450 [self.command.commandName, code]));
rgindafeaf3142012-01-31 15:14:20 -0800451 self.uninstallKeyboard();
rginda87b86462011-12-14 13:48:03 -0800452 }
453 });
454
rgindafeaf3142012-01-31 15:14:20 -0800455 this.installKeyboard();
rginda87b86462011-12-14 13:48:03 -0800456 this.command.run();
457};
458
459/**
rgindafeaf3142012-01-31 15:14:20 -0800460 * Returns true if the current screen is the primary screen, false otherwise.
461 */
462hterm.Terminal.prototype.isPrimaryScreen = function() {
rgindaf522ce02012-04-17 17:49:17 -0700463 return this.screen_ == this.primaryScreen_;
rgindafeaf3142012-01-31 15:14:20 -0800464};
465
466/**
467 * Install the keyboard handler for this terminal.
468 *
469 * This will prevent the browser from seeing any keystrokes sent to the
470 * terminal.
471 */
472hterm.Terminal.prototype.installKeyboard = function() {
473 this.keyboard.installKeyboard(this.document_.body.firstChild);
474}
475
476/**
477 * Uninstall the keyboard handler for this terminal.
478 */
479hterm.Terminal.prototype.uninstallKeyboard = function() {
480 this.keyboard.installKeyboard(null);
481}
482
483/**
rginda35c456b2012-02-09 17:29:05 -0800484 * Set the font size for this terminal.
rginda9f5222b2012-03-05 11:53:28 -0800485 *
486 * Call setFontSize(0) to reset to the default font size.
487 *
488 * This function does not modify the font-size preference.
489 *
490 * @param {number} px The desired font size, in pixels.
rginda35c456b2012-02-09 17:29:05 -0800491 */
492hterm.Terminal.prototype.setFontSize = function(px) {
rginda9f5222b2012-03-05 11:53:28 -0800493 if (px === 0)
494 px = this.prefs_.get('font-size');
495
rginda35c456b2012-02-09 17:29:05 -0800496 this.scrollPort_.setFontSize(px);
497};
498
499/**
500 * Get the current font size.
501 */
502hterm.Terminal.prototype.getFontSize = function() {
503 return this.scrollPort_.getFontSize();
504};
505
506/**
rginda8e92a692012-05-20 19:37:20 -0700507 * Get the current font family.
508 */
509hterm.Terminal.prototype.getFontFamily = function() {
510 return this.scrollPort_.getFontFamily();
511};
512
513/**
rginda35c456b2012-02-09 17:29:05 -0800514 * Set the CSS "font-family" for this terminal.
515 */
rginda9f5222b2012-03-05 11:53:28 -0800516hterm.Terminal.prototype.syncFontFamily = function() {
517 this.scrollPort_.setFontFamily(this.prefs_.get('font-family'),
518 this.prefs_.get('font-smoothing'));
519 this.syncBoldSafeState();
520};
521
522hterm.Terminal.prototype.syncBoldSafeState = function() {
523 var enableBold = this.prefs_.get('enable-bold');
524 if (enableBold !== null) {
525 this.screen_.textAttributes.enableBold = enableBold;
526 return;
527 }
528
rgindaf7521392012-02-28 17:20:34 -0800529 var normalSize = this.scrollPort_.measureCharacterSize();
530 var boldSize = this.scrollPort_.measureCharacterSize('bold');
531
532 var isBoldSafe = normalSize.equals(boldSize);
rgindaf7521392012-02-28 17:20:34 -0800533 if (!isBoldSafe) {
534 console.warn('Bold characters disabled: Size of bold weight differs ' +
rgindac9759de2012-03-19 13:21:41 -0700535 'from normal. Font family is: ' +
536 this.scrollPort_.getFontFamily());
rgindaf7521392012-02-28 17:20:34 -0800537 }
rginda9f5222b2012-03-05 11:53:28 -0800538
539 this.screen_.textAttributes.enableBold = isBoldSafe;
rginda35c456b2012-02-09 17:29:05 -0800540};
541
542/**
rginda87b86462011-12-14 13:48:03 -0800543 * Return a copy of the current cursor position.
544 *
545 * @return {hterm.RowCol} The RowCol object representing the current position.
546 */
547hterm.Terminal.prototype.saveCursor = function() {
548 return this.screen_.cursorPosition.clone();
549};
550
rgindaa19afe22012-01-25 15:40:22 -0800551hterm.Terminal.prototype.getTextAttributes = function() {
552 return this.screen_.textAttributes;
553};
554
rginda87b86462011-12-14 13:48:03 -0800555/**
rgindaf522ce02012-04-17 17:49:17 -0700556 * Return the current browser zoom factor applied to the terminal.
557 *
558 * @return {number} The current browser zoom factor.
559 */
560hterm.Terminal.prototype.getZoomFactor = function() {
561 return this.scrollPort_.characterSize.zoomFactor;
562};
563
564/**
rginda9846e2f2012-01-27 13:53:33 -0800565 * Change the title of this terminal's window.
566 */
567hterm.Terminal.prototype.setWindowTitle = function(title) {
rgindafeaf3142012-01-31 15:14:20 -0800568 window.document.title = title;
rginda9846e2f2012-01-27 13:53:33 -0800569};
570
571/**
rginda87b86462011-12-14 13:48:03 -0800572 * Restore a previously saved cursor position.
573 *
574 * @param {hterm.RowCol} cursor The position to restore.
575 */
576hterm.Terminal.prototype.restoreCursor = function(cursor) {
rgindacbbd7482012-06-13 15:06:16 -0700577 var row = lib.f.clamp(cursor.row, 0, this.screenSize.height - 1);
578 var column = lib.f.clamp(cursor.column, 0, this.screenSize.width - 1);
rginda35c456b2012-02-09 17:29:05 -0800579 this.screen_.setCursorPosition(row, column);
580 if (cursor.column > column ||
581 cursor.column == column && cursor.overflow) {
582 this.screen_.cursorPosition.overflow = true;
583 }
rginda87b86462011-12-14 13:48:03 -0800584};
585
586/**
David Benjamin54e8bf62012-06-01 22:31:40 -0400587 * Clear the cursor's overflow flag.
588 */
589hterm.Terminal.prototype.clearCursorOverflow = function() {
590 this.screen_.cursorPosition.overflow = false;
591};
592
593/**
rginda87b86462011-12-14 13:48:03 -0800594 * Set the width of the terminal, resizing the UI to match.
595 */
596hterm.Terminal.prototype.setWidth = function(columnCount) {
rgindaf0090c92012-02-10 14:58:52 -0800597 if (columnCount == null) {
598 this.div_.style.width = '100%';
599 return;
600 }
601
rginda35c456b2012-02-09 17:29:05 -0800602 this.div_.style.width = this.scrollPort_.characterSize.width *
603 columnCount + this.scrollbarWidthPx + 'px';
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +0400604 this.realizeSize_(columnCount, this.screenSize.height);
rgindac9bc5502012-01-18 11:48:44 -0800605 this.scheduleSyncCursorPosition_();
606};
rginda87b86462011-12-14 13:48:03 -0800607
rgindac9bc5502012-01-18 11:48:44 -0800608/**
rginda35c456b2012-02-09 17:29:05 -0800609 * Set the height of the terminal, resizing the UI to match.
610 */
611hterm.Terminal.prototype.setHeight = function(rowCount) {
rgindaf0090c92012-02-10 14:58:52 -0800612 if (rowCount == null) {
613 this.div_.style.height = '100%';
614 return;
615 }
616
rginda35c456b2012-02-09 17:29:05 -0800617 this.div_.style.height =
rginda30f20f62012-04-05 16:36:19 -0700618 this.scrollPort_.characterSize.height * rowCount + 'px';
rginda35c456b2012-02-09 17:29:05 -0800619 this.realizeSize_(this.screenSize.width, rowCount);
620 this.scheduleSyncCursorPosition_();
621};
622
623/**
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +0400624 * Deal with terminal size changes.
625 *
626 */
627hterm.Terminal.prototype.realizeSize_ = function(columnCount, rowCount) {
628 if (columnCount != this.screenSize.width)
629 this.realizeWidth_(columnCount);
630
631 if (rowCount != this.screenSize.height)
632 this.realizeHeight_(rowCount);
633
634 // Send new terminal size to plugin.
635 this.io.onTerminalResize(columnCount, rowCount);
636};
637
638/**
rgindac9bc5502012-01-18 11:48:44 -0800639 * Deal with terminal width changes.
640 *
641 * This function does what needs to be done when the terminal width changes
642 * out from under us. It happens here rather than in onResize_() because this
643 * code may need to run synchronously to handle programmatic changes of
644 * terminal width.
645 *
646 * Relying on the browser to send us an async resize event means we may not be
647 * in the correct state yet when the next escape sequence hits.
648 */
649hterm.Terminal.prototype.realizeWidth_ = function(columnCount) {
650 var deltaColumns = columnCount - this.screen_.getWidth();
651
rginda87b86462011-12-14 13:48:03 -0800652 this.screenSize.width = columnCount;
653 this.screen_.setColumnCount(columnCount);
rgindac9bc5502012-01-18 11:48:44 -0800654
655 if (deltaColumns > 0) {
David Benjamin66e954d2012-05-05 21:08:12 -0400656 if (this.defaultTabStops)
657 this.setDefaultTabStops(this.screenSize.width - deltaColumns);
rgindac9bc5502012-01-18 11:48:44 -0800658 } else {
659 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
David Benjamin66e954d2012-05-05 21:08:12 -0400660 if (this.tabStops_[i] < columnCount)
rgindac9bc5502012-01-18 11:48:44 -0800661 break;
662
663 this.tabStops_.pop();
664 }
665 }
666
667 this.screen_.setColumnCount(this.screenSize.width);
668};
669
670/**
671 * Deal with terminal height changes.
672 *
673 * This function does what needs to be done when the terminal height changes
674 * out from under us. It happens here rather than in onResize_() because this
675 * code may need to run synchronously to handle programmatic changes of
676 * terminal height.
677 *
678 * Relying on the browser to send us an async resize event means we may not be
679 * in the correct state yet when the next escape sequence hits.
680 */
681hterm.Terminal.prototype.realizeHeight_ = function(rowCount) {
682 var deltaRows = rowCount - this.screen_.getHeight();
683
684 this.screenSize.height = rowCount;
685
686 var cursor = this.saveCursor();
687
688 if (deltaRows < 0) {
689 // Screen got smaller.
690 deltaRows *= -1;
691 while (deltaRows) {
692 var lastRow = this.getRowCount() - 1;
693 if (lastRow - this.scrollbackRows_.length == cursor.row)
694 break;
695
696 if (this.getRowText(lastRow))
697 break;
698
699 this.screen_.popRow();
700 deltaRows--;
701 }
702
703 var ary = this.screen_.shiftRows(deltaRows);
704 this.scrollbackRows_.push.apply(this.scrollbackRows_, ary);
705
706 // We just removed rows from the top of the screen, we need to update
707 // the cursor to match.
rginda35c456b2012-02-09 17:29:05 -0800708 cursor.row = Math.max(cursor.row - deltaRows, 0);
rgindac9bc5502012-01-18 11:48:44 -0800709 } else if (deltaRows > 0) {
710 // Screen got larger.
711
712 if (deltaRows <= this.scrollbackRows_.length) {
713 var scrollbackCount = Math.min(deltaRows, this.scrollbackRows_.length);
714 var rows = this.scrollbackRows_.splice(
715 this.scrollbackRows_.length - scrollbackCount, scrollbackCount);
716 this.screen_.unshiftRows(rows);
717 deltaRows -= scrollbackCount;
718 cursor.row += scrollbackCount;
719 }
720
721 if (deltaRows)
722 this.appendRows_(deltaRows);
723 }
724
rginda35c456b2012-02-09 17:29:05 -0800725 this.setVTScrollRegion(null, null);
rgindac9bc5502012-01-18 11:48:44 -0800726 this.restoreCursor(cursor);
rginda87b86462011-12-14 13:48:03 -0800727};
728
729/**
730 * Scroll the terminal to the top of the scrollback buffer.
731 */
732hterm.Terminal.prototype.scrollHome = function() {
733 this.scrollPort_.scrollRowToTop(0);
734};
735
736/**
737 * Scroll the terminal to the end.
738 */
739hterm.Terminal.prototype.scrollEnd = function() {
740 this.scrollPort_.scrollRowToBottom(this.getRowCount());
741};
742
743/**
744 * Scroll the terminal one page up (minus one line) relative to the current
745 * position.
746 */
747hterm.Terminal.prototype.scrollPageUp = function() {
748 var i = this.scrollPort_.getTopRowIndex();
749 this.scrollPort_.scrollRowToTop(i - this.screenSize.height + 1);
750};
751
752/**
753 * Scroll the terminal one page down (minus one line) relative to the current
754 * position.
755 */
756hterm.Terminal.prototype.scrollPageDown = function() {
757 var i = this.scrollPort_.getTopRowIndex();
758 this.scrollPort_.scrollRowToTop(i + this.screenSize.height - 1);
rginda8ba33642011-12-14 12:31:31 -0800759};
760
rgindac9bc5502012-01-18 11:48:44 -0800761/**
762 * Full terminal reset.
763 */
rginda87b86462011-12-14 13:48:03 -0800764hterm.Terminal.prototype.reset = function() {
rgindac9bc5502012-01-18 11:48:44 -0800765 this.clearAllTabStops();
766 this.setDefaultTabStops();
rginda9ea433c2012-03-16 11:57:00 -0700767
768 this.clearHome(this.primaryScreen_);
769 this.primaryScreen_.textAttributes.reset();
770
771 this.clearHome(this.alternateScreen_);
772 this.alternateScreen_.textAttributes.reset();
773
rgindab8bc8932012-04-27 12:45:03 -0700774 this.setCursorBlink(!!this.prefs_.get('cursor-blink'));
775
rgindac9bc5502012-01-18 11:48:44 -0800776 this.softReset();
rginda87b86462011-12-14 13:48:03 -0800777};
778
rgindac9bc5502012-01-18 11:48:44 -0800779/**
780 * Soft terminal reset.
rgindab8bc8932012-04-27 12:45:03 -0700781 *
782 * Perform a soft reset to the default values listed in
783 * http://www.vt100.net/docs/vt510-rm/DECSTR#T5-9
rgindac9bc5502012-01-18 11:48:44 -0800784 */
rginda0f5c0292012-01-13 11:00:13 -0800785hterm.Terminal.prototype.softReset = function() {
rgindab8bc8932012-04-27 12:45:03 -0700786 // Reset terminal options to their default values.
rgindac9bc5502012-01-18 11:48:44 -0800787 this.options_ = new hterm.Options();
rgindaf522ce02012-04-17 17:49:17 -0700788
rgindab8bc8932012-04-27 12:45:03 -0700789 // Xterm also resets the color palette on soft reset, even though it doesn't
790 // seem to be documented anywhere.
rgindaf522ce02012-04-17 17:49:17 -0700791 this.primaryScreen_.textAttributes.resetColorPalette();
792 this.alternateScreen_.textAttributes.resetColorPalette();
793
rgindab8bc8932012-04-27 12:45:03 -0700794 // The xterm man page explicitly says this will happen on soft reset.
795 this.setVTScrollRegion(null, null);
796
797 // Xterm also shows the cursor on soft reset, but does not alter the blink
798 // state.
rgindaa19afe22012-01-25 15:40:22 -0800799 this.setCursorVisible(true);
rginda0f5c0292012-01-13 11:00:13 -0800800};
801
rgindac9bc5502012-01-18 11:48:44 -0800802/**
803 * Move the cursor forward to the next tab stop, or to the last column
804 * if no more tab stops are set.
805 */
806hterm.Terminal.prototype.forwardTabStop = function() {
807 var column = this.screen_.cursorPosition.column;
808
809 for (var i = 0; i < this.tabStops_.length; i++) {
810 if (this.tabStops_[i] > column) {
811 this.setCursorColumn(this.tabStops_[i]);
812 return;
813 }
814 }
815
David Benjamin66e954d2012-05-05 21:08:12 -0400816 // xterm does not clear the overflow flag on HT or CHT.
817 var overflow = this.screen_.cursorPosition.overflow;
rgindac9bc5502012-01-18 11:48:44 -0800818 this.setCursorColumn(this.screenSize.width - 1);
David Benjamin66e954d2012-05-05 21:08:12 -0400819 this.screen_.cursorPosition.overflow = overflow;
rginda0f5c0292012-01-13 11:00:13 -0800820};
821
rgindac9bc5502012-01-18 11:48:44 -0800822/**
823 * Move the cursor backward to the previous tab stop, or to the first column
824 * if no previous tab stops are set.
825 */
826hterm.Terminal.prototype.backwardTabStop = function() {
827 var column = this.screen_.cursorPosition.column;
828
829 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
830 if (this.tabStops_[i] < column) {
831 this.setCursorColumn(this.tabStops_[i]);
832 return;
833 }
834 }
835
836 this.setCursorColumn(1);
rginda0f5c0292012-01-13 11:00:13 -0800837};
838
rgindac9bc5502012-01-18 11:48:44 -0800839/**
840 * Set a tab stop at the given column.
841 *
842 * @param {int} column Zero based column.
843 */
844hterm.Terminal.prototype.setTabStop = function(column) {
845 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
846 if (this.tabStops_[i] == column)
847 return;
848
849 if (this.tabStops_[i] < column) {
850 this.tabStops_.splice(i + 1, 0, column);
851 return;
852 }
853 }
854
855 this.tabStops_.splice(0, 0, column);
rginda87b86462011-12-14 13:48:03 -0800856};
857
rgindac9bc5502012-01-18 11:48:44 -0800858/**
859 * Clear the tab stop at the current cursor position.
860 *
861 * No effect if there is no tab stop at the current cursor position.
862 */
863hterm.Terminal.prototype.clearTabStopAtCursor = function() {
864 var column = this.screen_.cursorPosition.column;
865
866 var i = this.tabStops_.indexOf(column);
867 if (i == -1)
868 return;
869
870 this.tabStops_.splice(i, 1);
871};
872
873/**
874 * Clear all tab stops.
875 */
876hterm.Terminal.prototype.clearAllTabStops = function() {
877 this.tabStops_.length = 0;
David Benjamin66e954d2012-05-05 21:08:12 -0400878 this.defaultTabStops = false;
rgindac9bc5502012-01-18 11:48:44 -0800879};
880
881/**
882 * Set up the default tab stops, starting from a given column.
883 *
884 * This sets a tabstop every (column % this.tabWidth) column, starting
David Benjamin66e954d2012-05-05 21:08:12 -0400885 * from the specified column, or 0 if no column is provided. It also flags
886 * future resizes to set them up.
rgindac9bc5502012-01-18 11:48:44 -0800887 *
888 * This does not clear the existing tab stops first, use clearAllTabStops
889 * for that.
890 *
891 * @param {int} opt_start Optional starting zero based starting column, useful
892 * for filling out missing tab stops when the terminal is resized.
893 */
894hterm.Terminal.prototype.setDefaultTabStops = function(opt_start) {
895 var start = opt_start || 0;
896 var w = this.tabWidth;
David Benjamin66e954d2012-05-05 21:08:12 -0400897 // Round start up to a default tab stop.
898 start = start - 1 - ((start - 1) % w) + w;
899 for (var i = start; i < this.screenSize.width; i += w) {
900 this.setTabStop(i);
rgindac9bc5502012-01-18 11:48:44 -0800901 }
David Benjamin66e954d2012-05-05 21:08:12 -0400902
903 this.defaultTabStops = true;
rginda87b86462011-12-14 13:48:03 -0800904};
905
rginda6d397402012-01-17 10:58:29 -0800906/**
907 * Save cursor position and attributes.
908 *
909 * TODO(rginda): Save attributes once we support them.
910 */
rginda87b86462011-12-14 13:48:03 -0800911hterm.Terminal.prototype.saveOptions = function() {
rginda6d397402012-01-17 10:58:29 -0800912 this.savedOptions_.cursor = this.saveCursor();
rgindaa19afe22012-01-25 15:40:22 -0800913 this.savedOptions_.textAttributes = this.screen_.textAttributes.clone();
rginda87b86462011-12-14 13:48:03 -0800914};
915
rginda6d397402012-01-17 10:58:29 -0800916/**
917 * Restore cursor position and attributes.
918 *
919 * TODO(rginda): Restore attributes once we support them.
920 */
rginda87b86462011-12-14 13:48:03 -0800921hterm.Terminal.prototype.restoreOptions = function() {
rginda6d397402012-01-17 10:58:29 -0800922 if (this.savedOptions_.cursor)
923 this.restoreCursor(this.savedOptions_.cursor);
rgindaa19afe22012-01-25 15:40:22 -0800924 if (this.savedOptions_.textAttributes)
925 this.screen_.textAttributes = this.savedOptions_.textAttributes;
rginda8ba33642011-12-14 12:31:31 -0800926};
927
928/**
929 * Interpret a sequence of characters.
930 *
931 * Incomplete escape sequences are buffered until the next call.
932 *
933 * @param {string} str Sequence of characters to interpret or pass through.
934 */
935hterm.Terminal.prototype.interpret = function(str) {
rginda0f5c0292012-01-13 11:00:13 -0800936 this.vt.interpret(str);
rginda8ba33642011-12-14 12:31:31 -0800937 this.scheduleSyncCursorPosition_();
938};
939
940/**
941 * Take over the given DIV for use as the terminal display.
942 *
943 * @param {HTMLDivElement} div The div to use as the terminal display.
944 */
945hterm.Terminal.prototype.decorate = function(div) {
rginda87b86462011-12-14 13:48:03 -0800946 this.div_ = div;
947
rginda8ba33642011-12-14 12:31:31 -0800948 this.scrollPort_.decorate(div);
rginda30f20f62012-04-05 16:36:19 -0700949 this.scrollPort_.setBackgroundImage(this.prefs_.get('background-image'));
Philip Douglass959b49d2012-05-30 13:29:29 -0400950 this.scrollPort_.setBackgroundSize(this.prefs_.get('background-size'));
951 this.scrollPort_.setBackgroundPosition(
952 this.prefs_.get('background-position'));
rginda30f20f62012-04-05 16:36:19 -0700953
rginda0918b652012-04-04 11:26:24 -0700954 this.div_.focus = this.focus.bind(this);
rgindaf7521392012-02-28 17:20:34 -0800955
rginda9f5222b2012-03-05 11:53:28 -0800956 this.setFontSize(this.prefs_.get('font-size'));
957 this.syncFontFamily();
rgindaa19afe22012-01-25 15:40:22 -0800958
David Reveman8f552492012-03-28 12:18:41 -0400959 this.setScrollbarVisible(this.prefs_.get('scrollbar-visible'));
960
rginda8ba33642011-12-14 12:31:31 -0800961 this.document_ = this.scrollPort_.getDocument();
962
rginda8e92a692012-05-20 19:37:20 -0700963 this.document_.body.firstChild.addEventListener(
964 'focus', this.onFocusChange_.bind(this, true));
965 this.document_.body.firstChild.addEventListener(
966 'blur', this.onFocusChange_.bind(this, false));
967
968 var style = this.document_.createElement('style');
969 style.textContent =
970 ('.cursor-node[focus="false"] {' +
971 ' box-sizing: border-box;' +
972 ' background-color: transparent !important;' +
973 ' border-width: 2px;' +
974 ' border-style: solid;' +
975 '}');
976 this.document_.head.appendChild(style);
977
rginda8ba33642011-12-14 12:31:31 -0800978 this.cursorNode_ = this.document_.createElement('div');
rginda8e92a692012-05-20 19:37:20 -0700979 this.cursorNode_.className = 'cursor-node';
rginda8ba33642011-12-14 12:31:31 -0800980 this.cursorNode_.style.cssText =
981 ('position: absolute;' +
rginda87b86462011-12-14 13:48:03 -0800982 'top: -99px;' +
983 'display: block;' +
rginda35c456b2012-02-09 17:29:05 -0800984 'width: ' + this.scrollPort_.characterSize.width + 'px;' +
985 'height: ' + this.scrollPort_.characterSize.height + 'px;' +
rginda8e92a692012-05-20 19:37:20 -0700986 '-webkit-transition: opacity, background-color 100ms linear;');
987 this.setCursorColor(this.prefs_.get('cursor-color'));
rginda8ba33642011-12-14 12:31:31 -0800988 this.document_.body.appendChild(this.cursorNode_);
989
rgindade84e382012-04-20 15:39:31 -0700990 this.setCursorBlink(!!this.prefs_.get('cursor-blink'));
rginda8ba33642011-12-14 12:31:31 -0800991 this.setReverseVideo(false);
rginda87b86462011-12-14 13:48:03 -0800992
rginda87b86462011-12-14 13:48:03 -0800993 this.scrollPort_.focus();
rginda6d397402012-01-17 10:58:29 -0800994 this.scrollPort_.scheduleRedraw();
rginda87b86462011-12-14 13:48:03 -0800995};
996
rginda0918b652012-04-04 11:26:24 -0700997/**
998 * Return the HTML document that contains the terminal DOM nodes.
999 */
rginda87b86462011-12-14 13:48:03 -08001000hterm.Terminal.prototype.getDocument = function() {
1001 return this.document_;
rginda8ba33642011-12-14 12:31:31 -08001002};
1003
1004/**
rginda0918b652012-04-04 11:26:24 -07001005 * Focus the terminal.
1006 */
1007hterm.Terminal.prototype.focus = function() {
1008 this.scrollPort_.focus();
1009};
1010
1011/**
rginda8ba33642011-12-14 12:31:31 -08001012 * Return the HTML Element for a given row index.
1013 *
1014 * This is a method from the RowProvider interface. The ScrollPort uses
1015 * it to fetch rows on demand as they are scrolled into view.
1016 *
1017 * TODO(rginda): Consider saving scrollback rows as (HTML source, text content)
1018 * pairs to conserve memory.
1019 *
1020 * @param {integer} index The zero-based row index, measured relative to the
1021 * start of the scrollback buffer. On-screen rows will always have the
1022 * largest indicies.
1023 * @return {HTMLElement} The 'x-row' element containing for the requested row.
1024 */
1025hterm.Terminal.prototype.getRowNode = function(index) {
1026 if (index < this.scrollbackRows_.length)
1027 return this.scrollbackRows_[index];
1028
1029 var screenIndex = index - this.scrollbackRows_.length;
1030 return this.screen_.rowsArray[screenIndex];
1031};
1032
1033/**
1034 * Return the text content for a given range of rows.
1035 *
1036 * This is a method from the RowProvider interface. The ScrollPort uses
1037 * it to fetch text content on demand when the user attempts to copy their
1038 * selection to the clipboard.
1039 *
1040 * @param {integer} start The zero-based row index to start from, measured
1041 * relative to the start of the scrollback buffer. On-screen rows will
1042 * always have the largest indicies.
1043 * @param {integer} end The zero-based row index to end on, measured
1044 * relative to the start of the scrollback buffer.
1045 * @return {string} A single string containing the text value of the range of
1046 * rows. Lines will be newline delimited, with no trailing newline.
1047 */
1048hterm.Terminal.prototype.getRowsText = function(start, end) {
1049 var ary = [];
1050 for (var i = start; i < end; i++) {
1051 var node = this.getRowNode(i);
1052 ary.push(node.textContent);
1053 }
1054
1055 return ary.join('\n');
1056};
1057
1058/**
1059 * Return the text content for a given row.
1060 *
1061 * This is a method from the RowProvider interface. The ScrollPort uses
1062 * it to fetch text content on demand when the user attempts to copy their
1063 * selection to the clipboard.
1064 *
1065 * @param {integer} index The zero-based row index to return, measured
1066 * relative to the start of the scrollback buffer. On-screen rows will
1067 * always have the largest indicies.
1068 * @return {string} A string containing the text value of the selected row.
1069 */
1070hterm.Terminal.prototype.getRowText = function(index) {
1071 var node = this.getRowNode(index);
rginda87b86462011-12-14 13:48:03 -08001072 return node.textContent;
rginda8ba33642011-12-14 12:31:31 -08001073};
1074
1075/**
1076 * Return the total number of rows in the addressable screen and in the
1077 * scrollback buffer of this terminal.
1078 *
1079 * This is a method from the RowProvider interface. The ScrollPort uses
1080 * it to compute the size of the scrollbar.
1081 *
1082 * @return {integer} The number of rows in this terminal.
1083 */
1084hterm.Terminal.prototype.getRowCount = function() {
1085 return this.scrollbackRows_.length + this.screen_.rowsArray.length;
1086};
1087
1088/**
1089 * Create DOM nodes for new rows and append them to the end of the terminal.
1090 *
1091 * This is the only correct way to add a new DOM node for a row. Notice that
1092 * the new row is appended to the bottom of the list of rows, and does not
1093 * require renumbering (of the rowIndex property) of previous rows.
1094 *
1095 * If you think you want a new blank row somewhere in the middle of the
1096 * terminal, look into moveRows_().
1097 *
1098 * This method does not pay attention to vtScrollTop/Bottom, since you should
1099 * be using moveRows() in cases where they would matter.
1100 *
1101 * The cursor will be positioned at column 0 of the first inserted line.
1102 */
1103hterm.Terminal.prototype.appendRows_ = function(count) {
1104 var cursorRow = this.screen_.rowsArray.length;
1105 var offset = this.scrollbackRows_.length + cursorRow;
1106 for (var i = 0; i < count; i++) {
1107 var row = this.document_.createElement('x-row');
1108 row.appendChild(this.document_.createTextNode(''));
1109 row.rowIndex = offset + i;
1110 this.screen_.pushRow(row);
1111 }
1112
1113 var extraRows = this.screen_.rowsArray.length - this.screenSize.height;
1114 if (extraRows > 0) {
1115 var ary = this.screen_.shiftRows(extraRows);
1116 Array.prototype.push.apply(this.scrollbackRows_, ary);
1117 this.scheduleScrollDown_();
1118 }
1119
1120 if (cursorRow >= this.screen_.rowsArray.length)
1121 cursorRow = this.screen_.rowsArray.length - 1;
1122
rginda87b86462011-12-14 13:48:03 -08001123 this.setAbsoluteCursorPosition(cursorRow, 0);
rginda8ba33642011-12-14 12:31:31 -08001124};
1125
1126/**
1127 * Relocate rows from one part of the addressable screen to another.
1128 *
1129 * This is used to recycle rows during VT scrolls (those which are driven
1130 * by VT commands, rather than by the user manipulating the scrollbar.)
1131 *
1132 * In this case, the blank lines scrolled into the scroll region are made of
1133 * the nodes we scrolled off. These have their rowIndex properties carefully
1134 * renumbered so as not to confuse the ScrollPort.
rginda8ba33642011-12-14 12:31:31 -08001135 */
1136hterm.Terminal.prototype.moveRows_ = function(fromIndex, count, toIndex) {
1137 var ary = this.screen_.removeRows(fromIndex, count);
1138 this.screen_.insertRows(toIndex, ary);
1139
1140 var start, end;
1141 if (fromIndex < toIndex) {
1142 start = fromIndex;
rginda87b86462011-12-14 13:48:03 -08001143 end = toIndex + count;
rginda8ba33642011-12-14 12:31:31 -08001144 } else {
1145 start = toIndex;
rginda87b86462011-12-14 13:48:03 -08001146 end = fromIndex + count;
rginda8ba33642011-12-14 12:31:31 -08001147 }
1148
1149 this.renumberRows_(start, end);
rginda2312fff2012-01-05 16:20:52 -08001150 this.scrollPort_.scheduleInvalidate();
rginda8ba33642011-12-14 12:31:31 -08001151};
1152
1153/**
1154 * Renumber the rowIndex property of the given range of rows.
1155 *
1156 * The start and end indicies are relative to the screen, not the scrollback.
1157 * Rows in the scrollback buffer cannot be renumbered. Since they are not
rginda2312fff2012-01-05 16:20:52 -08001158 * addressable (you can't delete them, scroll them, etc), you should have
rginda8ba33642011-12-14 12:31:31 -08001159 * no need to renumber scrollback rows.
1160 */
1161hterm.Terminal.prototype.renumberRows_ = function(start, end) {
1162 var offset = this.scrollbackRows_.length;
1163 for (var i = start; i < end; i++) {
1164 this.screen_.rowsArray[i].rowIndex = offset + i;
1165 }
1166};
1167
1168/**
1169 * Print a string to the terminal.
1170 *
1171 * This respects the current insert and wraparound modes. It will add new lines
1172 * to the end of the terminal, scrolling off the top into the scrollback buffer
1173 * if necessary.
1174 *
1175 * The string is *not* parsed for escape codes. Use the interpret() method if
1176 * that's what you're after.
1177 *
1178 * @param{string} str The string to print.
1179 */
1180hterm.Terminal.prototype.print = function(str) {
rgindaa19afe22012-01-25 15:40:22 -08001181 if (this.options_.wraparound && this.screen_.cursorPosition.overflow)
1182 this.newLine();
rginda2312fff2012-01-05 16:20:52 -08001183
rgindaa19afe22012-01-25 15:40:22 -08001184 if (this.options_.insertMode) {
1185 this.screen_.insertString(str);
1186 } else {
1187 this.screen_.overwriteString(str);
1188 }
1189
1190 var overflow = this.screen_.maybeClipCurrentRow();
1191
1192 if (this.options_.wraparound && overflow) {
1193 var lastColumn;
1194
1195 do {
rginda35c456b2012-02-09 17:29:05 -08001196 this.newLine();
1197 lastColumn = overflow.characterLength;
rgindaa19afe22012-01-25 15:40:22 -08001198
1199 if (!this.options_.insertMode)
1200 this.screen_.deleteChars(overflow.characterLength);
1201
1202 this.screen_.prependNodes(overflow);
rgindaa19afe22012-01-25 15:40:22 -08001203
1204 overflow = this.screen_.maybeClipCurrentRow();
1205 } while (overflow);
1206
1207 this.setCursorColumn(lastColumn);
1208 }
rginda8ba33642011-12-14 12:31:31 -08001209
1210 this.scheduleSyncCursorPosition_();
rginda0f5c0292012-01-13 11:00:13 -08001211
rginda9f5222b2012-03-05 11:53:28 -08001212 if (this.scrollOnOutput_)
rginda0f5c0292012-01-13 11:00:13 -08001213 this.scrollPort_.scrollRowToBottom(this.getRowCount());
rginda8ba33642011-12-14 12:31:31 -08001214};
1215
1216/**
rginda87b86462011-12-14 13:48:03 -08001217 * Set the VT scroll region.
1218 *
rginda87b86462011-12-14 13:48:03 -08001219 * This also resets the cursor position to the absolute (0, 0) position, since
1220 * that's what xterm appears to do.
1221 *
1222 * @param {integer} scrollTop The zero-based top of the scroll region.
1223 * @param {integer} scrollBottom The zero-based bottom of the scroll region,
1224 * inclusive.
1225 */
1226hterm.Terminal.prototype.setVTScrollRegion = function(scrollTop, scrollBottom) {
1227 this.vtScrollTop_ = scrollTop;
1228 this.vtScrollBottom_ = scrollBottom;
rginda87b86462011-12-14 13:48:03 -08001229};
1230
1231/**
rginda8ba33642011-12-14 12:31:31 -08001232 * Return the top row index according to the VT.
1233 *
1234 * This will return 0 unless the terminal has been told to restrict scrolling
1235 * to some lower row. It is used for some VT cursor positioning and scrolling
1236 * commands.
1237 *
1238 * @return {integer} The topmost row in the terminal's scroll region.
1239 */
1240hterm.Terminal.prototype.getVTScrollTop = function() {
1241 if (this.vtScrollTop_ != null)
1242 return this.vtScrollTop_;
1243
1244 return 0;
rginda87b86462011-12-14 13:48:03 -08001245};
rginda8ba33642011-12-14 12:31:31 -08001246
1247/**
1248 * Return the bottom row index according to the VT.
1249 *
1250 * This will return the height of the terminal unless the it has been told to
1251 * restrict scrolling to some higher row. It is used for some VT cursor
1252 * positioning and scrolling commands.
1253 *
1254 * @return {integer} The bottommost row in the terminal's scroll region.
1255 */
1256hterm.Terminal.prototype.getVTScrollBottom = function() {
1257 if (this.vtScrollBottom_ != null)
1258 return this.vtScrollBottom_;
1259
rginda87b86462011-12-14 13:48:03 -08001260 return this.screenSize.height - 1;
rginda8ba33642011-12-14 12:31:31 -08001261}
1262
1263/**
1264 * Process a '\n' character.
1265 *
1266 * If the cursor is on the final row of the terminal this will append a new
1267 * blank row to the screen and scroll the topmost row into the scrollback
1268 * buffer.
1269 *
1270 * Otherwise, this moves the cursor to column zero of the next row.
1271 */
1272hterm.Terminal.prototype.newLine = function() {
1273 if (this.screen_.cursorPosition.row == this.screen_.rowsArray.length - 1) {
rginda87b86462011-12-14 13:48:03 -08001274 // If we're at the end of the screen we need to append a new line and
1275 // scroll the top line into the scrollback buffer.
rginda8ba33642011-12-14 12:31:31 -08001276 this.appendRows_(1);
rginda87b86462011-12-14 13:48:03 -08001277 } else if (this.screen_.cursorPosition.row == this.getVTScrollBottom()) {
1278 // End of the scroll region does not affect the scrollback buffer.
1279 this.vtScrollUp(1);
1280 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, 0);
rginda8ba33642011-12-14 12:31:31 -08001281 } else {
rginda87b86462011-12-14 13:48:03 -08001282 // Anywhere else in the screen just moves the cursor.
1283 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row + 1, 0);
rginda8ba33642011-12-14 12:31:31 -08001284 }
1285};
1286
1287/**
1288 * Like newLine(), except maintain the cursor column.
1289 */
1290hterm.Terminal.prototype.lineFeed = function() {
1291 var column = this.screen_.cursorPosition.column;
1292 this.newLine();
1293 this.setCursorColumn(column);
1294};
1295
1296/**
rginda87b86462011-12-14 13:48:03 -08001297 * If autoCarriageReturn is set then newLine(), else lineFeed().
1298 */
1299hterm.Terminal.prototype.formFeed = function() {
1300 if (this.options_.autoCarriageReturn) {
1301 this.newLine();
1302 } else {
1303 this.lineFeed();
1304 }
1305};
1306
1307/**
1308 * Move the cursor up one row, possibly inserting a blank line.
1309 *
1310 * The cursor column is not changed.
1311 */
1312hterm.Terminal.prototype.reverseLineFeed = function() {
1313 var scrollTop = this.getVTScrollTop();
1314 var currentRow = this.screen_.cursorPosition.row;
1315
1316 if (currentRow == scrollTop) {
1317 this.insertLines(1);
1318 } else {
1319 this.setAbsoluteCursorRow(currentRow - 1);
1320 }
1321};
1322
1323/**
rginda8ba33642011-12-14 12:31:31 -08001324 * Replace all characters to the left of the current cursor with the space
1325 * character.
1326 *
1327 * TODO(rginda): This should probably *remove* the characters (not just replace
1328 * with a space) if there are no characters at or beyond the current cursor
1329 * position. Once it does that, it'll have the same text-attribute related
1330 * issues as hterm.Screen.prototype.clearCursorRow :/
1331 */
1332hterm.Terminal.prototype.eraseToLeft = function() {
rginda87b86462011-12-14 13:48:03 -08001333 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001334 this.setCursorColumn(0);
rgindacbbd7482012-06-13 15:06:16 -07001335 this.screen_.overwriteString(lib.f.getWhitespace(cursor.column + 1));
rginda87b86462011-12-14 13:48:03 -08001336 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001337};
1338
1339/**
David Benjamin684a9b72012-05-01 17:19:58 -04001340 * Erase a given number of characters to the right of the cursor.
rginda8ba33642011-12-14 12:31:31 -08001341 *
1342 * The cursor position is unchanged.
1343 *
1344 * TODO(rginda): Test that this works even when the cursor is positioned beyond
1345 * the end of the text.
1346 *
1347 * TODO(rginda): This likely has text-attribute related troubles similar to the
1348 * todo on hterm.Screen.prototype.clearCursorRow.
David Benjamin684a9b72012-05-01 17:19:58 -04001349 *
1350 * TODO(davidben): Probably better to not add the whitespace to the clipboard
1351 * if erasing to the end of the drawn portion of the line. That said, xterm
1352 * behaves the same here.
rginda8ba33642011-12-14 12:31:31 -08001353 */
1354hterm.Terminal.prototype.eraseToRight = function(opt_count) {
rginda87b86462011-12-14 13:48:03 -08001355 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001356
rginda87b86462011-12-14 13:48:03 -08001357 var maxCount = this.screenSize.width - cursor.column;
David Benjamin684a9b72012-05-01 17:19:58 -04001358 if (opt_count === undefined || opt_count >= maxCount) {
1359 this.screen_.deleteChars(maxCount);
1360 } else {
rgindacbbd7482012-06-13 15:06:16 -07001361 this.screen_.overwriteString(lib.f.getWhitespace(opt_count));
David Benjamin684a9b72012-05-01 17:19:58 -04001362 }
rginda87b86462011-12-14 13:48:03 -08001363 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001364 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001365};
1366
1367/**
1368 * Erase the current line.
1369 *
1370 * The cursor position is unchanged.
1371 *
1372 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1373 * has a text-attribute related TODO.
1374 */
1375hterm.Terminal.prototype.eraseLine = function() {
rginda87b86462011-12-14 13:48:03 -08001376 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001377 this.screen_.clearCursorRow();
rginda87b86462011-12-14 13:48:03 -08001378 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001379 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001380};
1381
1382/**
David Benjamina08d78f2012-05-05 00:28:49 -04001383 * Erase all characters from the start of the screen to the current cursor
1384 * position, regardless of scroll region.
rginda8ba33642011-12-14 12:31:31 -08001385 *
1386 * The cursor position is unchanged.
1387 *
1388 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1389 * has a text-attribute related TODO.
1390 */
1391hterm.Terminal.prototype.eraseAbove = function() {
rginda87b86462011-12-14 13:48:03 -08001392 var cursor = this.saveCursor();
1393
1394 this.eraseToLeft();
rginda8ba33642011-12-14 12:31:31 -08001395
David Benjamina08d78f2012-05-05 00:28:49 -04001396 for (var i = 0; i < cursor.row; i++) {
rginda87b86462011-12-14 13:48:03 -08001397 this.setAbsoluteCursorPosition(i, 0);
rginda8ba33642011-12-14 12:31:31 -08001398 this.screen_.clearCursorRow();
1399 }
1400
rginda87b86462011-12-14 13:48:03 -08001401 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001402 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001403};
1404
1405/**
1406 * Erase all characters from the current cursor position to the end of the
David Benjamina08d78f2012-05-05 00:28:49 -04001407 * screen, regardless of scroll region.
rginda8ba33642011-12-14 12:31:31 -08001408 *
1409 * The cursor position is unchanged.
1410 *
1411 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1412 * has a text-attribute related TODO.
1413 */
1414hterm.Terminal.prototype.eraseBelow = function() {
rginda87b86462011-12-14 13:48:03 -08001415 var cursor = this.saveCursor();
1416
1417 this.eraseToRight();
rginda8ba33642011-12-14 12:31:31 -08001418
David Benjamina08d78f2012-05-05 00:28:49 -04001419 var bottom = this.screenSize.height - 1;
rginda87b86462011-12-14 13:48:03 -08001420 for (var i = cursor.row + 1; i <= bottom; i++) {
1421 this.setAbsoluteCursorPosition(i, 0);
rginda8ba33642011-12-14 12:31:31 -08001422 this.screen_.clearCursorRow();
1423 }
1424
rginda87b86462011-12-14 13:48:03 -08001425 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001426 this.clearCursorOverflow();
rginda87b86462011-12-14 13:48:03 -08001427};
1428
1429/**
1430 * Fill the terminal with a given character.
1431 *
1432 * This methods does not respect the VT scroll region.
1433 *
1434 * @param {string} ch The character to use for the fill.
1435 */
1436hterm.Terminal.prototype.fill = function(ch) {
1437 var cursor = this.saveCursor();
1438
1439 this.setAbsoluteCursorPosition(0, 0);
1440 for (var row = 0; row < this.screenSize.height; row++) {
1441 for (var col = 0; col < this.screenSize.width; col++) {
1442 this.setAbsoluteCursorPosition(row, col);
1443 this.screen_.overwriteString(ch);
1444 }
1445 }
1446
1447 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001448};
1449
1450/**
rginda9ea433c2012-03-16 11:57:00 -07001451 * Erase the entire display and leave the cursor at (0, 0).
rginda8ba33642011-12-14 12:31:31 -08001452 *
rginda9ea433c2012-03-16 11:57:00 -07001453 * This does not respect the scroll region.
1454 *
1455 * @param {hterm.Screen} opt_screen Optional screen to operate on. Defaults
1456 * to the current screen.
rginda8ba33642011-12-14 12:31:31 -08001457 *
1458 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1459 * has a text-attribute related TODO.
1460 */
rginda9ea433c2012-03-16 11:57:00 -07001461hterm.Terminal.prototype.clearHome = function(opt_screen) {
1462 var screen = opt_screen || this.screen_;
1463 var bottom = screen.getHeight();
rginda8ba33642011-12-14 12:31:31 -08001464
rginda11057d52012-04-25 12:29:56 -07001465 if (bottom == 0) {
1466 // Empty screen, nothing to do.
1467 return;
1468 }
1469
rgindae4d29232012-01-19 10:47:13 -08001470 for (var i = 0; i < bottom; i++) {
rginda9ea433c2012-03-16 11:57:00 -07001471 screen.setCursorPosition(i, 0);
1472 screen.clearCursorRow();
rginda8ba33642011-12-14 12:31:31 -08001473 }
1474
rginda9ea433c2012-03-16 11:57:00 -07001475 screen.setCursorPosition(0, 0);
1476};
1477
1478/**
1479 * Erase the entire display without changing the cursor position.
1480 *
1481 * The cursor position is unchanged. This does not respect the scroll
1482 * region.
1483 *
1484 * @param {hterm.Screen} opt_screen Optional screen to operate on. Defaults
1485 * to the current screen.
1486 *
1487 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1488 * has a text-attribute related TODO.
1489 */
1490hterm.Terminal.prototype.clear = function(opt_screen) {
1491 var screen = opt_screen || this.screen_;
1492 var cursor = screen.cursorPosition.clone();
1493 this.clearHome(screen);
1494 screen.setCursorPosition(cursor.row, cursor.column);
rginda8ba33642011-12-14 12:31:31 -08001495};
1496
1497/**
1498 * VT command to insert lines at the current cursor row.
1499 *
1500 * This respects the current scroll region. Rows pushed off the bottom are
1501 * lost (they won't show up in the scrollback buffer).
1502 *
1503 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1504 * has a text-attribute related TODO.
1505 *
1506 * @param {integer} count The number of lines to insert.
1507 */
1508hterm.Terminal.prototype.insertLines = function(count) {
rginda87b86462011-12-14 13:48:03 -08001509 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001510
1511 var bottom = this.getVTScrollBottom();
rginda87b86462011-12-14 13:48:03 -08001512 count = Math.min(count, bottom - cursor.row);
rginda8ba33642011-12-14 12:31:31 -08001513
rgindae4d29232012-01-19 10:47:13 -08001514 var start = bottom - count + 1;
rginda87b86462011-12-14 13:48:03 -08001515 if (start != cursor.row)
1516 this.moveRows_(start, count, cursor.row);
rginda8ba33642011-12-14 12:31:31 -08001517
1518 for (var i = 0; i < count; i++) {
rginda87b86462011-12-14 13:48:03 -08001519 this.setAbsoluteCursorPosition(cursor.row + i, 0);
rginda8ba33642011-12-14 12:31:31 -08001520 this.screen_.clearCursorRow();
1521 }
1522
rginda87b86462011-12-14 13:48:03 -08001523 cursor.column = 0;
1524 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001525};
1526
1527/**
1528 * VT command to delete lines at the current cursor row.
1529 *
1530 * New rows are added to the bottom of scroll region to take their place. New
1531 * rows are strictly there to take up space and have no content or style.
1532 */
1533hterm.Terminal.prototype.deleteLines = function(count) {
rginda87b86462011-12-14 13:48:03 -08001534 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001535
rginda87b86462011-12-14 13:48:03 -08001536 var top = cursor.row;
rginda8ba33642011-12-14 12:31:31 -08001537 var bottom = this.getVTScrollBottom();
1538
rginda87b86462011-12-14 13:48:03 -08001539 var maxCount = bottom - top + 1;
rginda8ba33642011-12-14 12:31:31 -08001540 count = Math.min(count, maxCount);
1541
rginda87b86462011-12-14 13:48:03 -08001542 var moveStart = bottom - count + 1;
rginda8ba33642011-12-14 12:31:31 -08001543 if (count != maxCount)
1544 this.moveRows_(top, count, moveStart);
1545
1546 for (var i = 0; i < count; i++) {
rginda87b86462011-12-14 13:48:03 -08001547 this.setAbsoluteCursorPosition(moveStart + i, 0);
rginda8ba33642011-12-14 12:31:31 -08001548 this.screen_.clearCursorRow();
1549 }
1550
rginda87b86462011-12-14 13:48:03 -08001551 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001552 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001553};
1554
1555/**
1556 * Inserts the given number of spaces at the current cursor position.
1557 *
rginda87b86462011-12-14 13:48:03 -08001558 * The cursor position is not changed.
rginda8ba33642011-12-14 12:31:31 -08001559 */
1560hterm.Terminal.prototype.insertSpace = function(count) {
rginda87b86462011-12-14 13:48:03 -08001561 var cursor = this.saveCursor();
1562
rgindacbbd7482012-06-13 15:06:16 -07001563 var ws = lib.f.getWhitespace(count || 1);
rginda8ba33642011-12-14 12:31:31 -08001564 this.screen_.insertString(ws);
rgindaa19afe22012-01-25 15:40:22 -08001565 this.screen_.maybeClipCurrentRow();
rginda87b86462011-12-14 13:48:03 -08001566
1567 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001568 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001569};
1570
1571/**
1572 * Forward-delete the specified number of characters starting at the cursor
1573 * position.
1574 *
1575 * @param {integer} count The number of characters to delete.
1576 */
1577hterm.Terminal.prototype.deleteChars = function(count) {
1578 this.screen_.deleteChars(count);
David Benjamin54e8bf62012-06-01 22:31:40 -04001579 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001580};
1581
1582/**
1583 * Shift rows in the scroll region upwards by a given number of lines.
1584 *
1585 * New rows are inserted at the bottom of the scroll region to fill the
1586 * vacated rows. The new rows not filled out with the current text attributes.
1587 *
1588 * This function does not affect the scrollback rows at all. Rows shifted
1589 * off the top are lost.
1590 *
rginda87b86462011-12-14 13:48:03 -08001591 * The cursor position is not altered.
1592 *
rginda8ba33642011-12-14 12:31:31 -08001593 * @param {integer} count The number of rows to scroll.
1594 */
1595hterm.Terminal.prototype.vtScrollUp = function(count) {
rginda87b86462011-12-14 13:48:03 -08001596 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001597
rginda87b86462011-12-14 13:48:03 -08001598 this.setAbsoluteCursorRow(this.getVTScrollTop());
rginda8ba33642011-12-14 12:31:31 -08001599 this.deleteLines(count);
1600
rginda87b86462011-12-14 13:48:03 -08001601 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001602};
1603
1604/**
1605 * Shift rows below the cursor down by a given number of lines.
1606 *
1607 * This function respects the current scroll region.
1608 *
1609 * New rows are inserted at the top of the scroll region to fill the
1610 * vacated rows. The new rows not filled out with the current text attributes.
1611 *
1612 * This function does not affect the scrollback rows at all. Rows shifted
1613 * off the bottom are lost.
1614 *
1615 * @param {integer} count The number of rows to scroll.
1616 */
1617hterm.Terminal.prototype.vtScrollDown = function(opt_count) {
rginda87b86462011-12-14 13:48:03 -08001618 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001619
rginda87b86462011-12-14 13:48:03 -08001620 this.setAbsoluteCursorPosition(this.getVTScrollTop(), 0);
rginda8ba33642011-12-14 12:31:31 -08001621 this.insertLines(opt_count);
1622
rginda87b86462011-12-14 13:48:03 -08001623 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001624};
1625
rginda87b86462011-12-14 13:48:03 -08001626
rginda8ba33642011-12-14 12:31:31 -08001627/**
1628 * Set the cursor position.
1629 *
1630 * The cursor row is relative to the scroll region if the terminal has
1631 * 'origin mode' enabled, or relative to the addressable screen otherwise.
1632 *
1633 * @param {integer} row The new zero-based cursor row.
1634 * @param {integer} row The new zero-based cursor column.
1635 */
1636hterm.Terminal.prototype.setCursorPosition = function(row, column) {
1637 if (this.options_.originMode) {
rginda87b86462011-12-14 13:48:03 -08001638 this.setRelativeCursorPosition(row, column);
rginda8ba33642011-12-14 12:31:31 -08001639 } else {
rginda87b86462011-12-14 13:48:03 -08001640 this.setAbsoluteCursorPosition(row, column);
rginda8ba33642011-12-14 12:31:31 -08001641 }
rginda87b86462011-12-14 13:48:03 -08001642};
rginda8ba33642011-12-14 12:31:31 -08001643
rginda87b86462011-12-14 13:48:03 -08001644hterm.Terminal.prototype.setRelativeCursorPosition = function(row, column) {
1645 var scrollTop = this.getVTScrollTop();
rgindacbbd7482012-06-13 15:06:16 -07001646 row = lib.f.clamp(row + scrollTop, scrollTop, this.getVTScrollBottom());
1647 column = lib.f.clamp(column, 0, this.screenSize.width - 1);
rginda87b86462011-12-14 13:48:03 -08001648 this.screen_.setCursorPosition(row, column);
1649};
1650
1651hterm.Terminal.prototype.setAbsoluteCursorPosition = function(row, column) {
rgindacbbd7482012-06-13 15:06:16 -07001652 row = lib.f.clamp(row, 0, this.screenSize.height - 1);
1653 column = lib.f.clamp(column, 0, this.screenSize.width - 1);
rginda8ba33642011-12-14 12:31:31 -08001654 this.screen_.setCursorPosition(row, column);
1655};
1656
1657/**
1658 * Set the cursor column.
1659 *
1660 * @param {integer} column The new zero-based cursor column.
1661 */
1662hterm.Terminal.prototype.setCursorColumn = function(column) {
rginda87b86462011-12-14 13:48:03 -08001663 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, column);
rginda8ba33642011-12-14 12:31:31 -08001664};
1665
1666/**
1667 * Return the cursor column.
1668 *
1669 * @return {integer} The zero-based cursor column.
1670 */
1671hterm.Terminal.prototype.getCursorColumn = function() {
1672 return this.screen_.cursorPosition.column;
1673};
1674
1675/**
1676 * Set the cursor row.
1677 *
1678 * The cursor row is relative to the scroll region if the terminal has
1679 * 'origin mode' enabled, or relative to the addressable screen otherwise.
1680 *
1681 * @param {integer} row The new cursor row.
1682 */
rginda87b86462011-12-14 13:48:03 -08001683hterm.Terminal.prototype.setAbsoluteCursorRow = function(row) {
1684 this.setAbsoluteCursorPosition(row, this.screen_.cursorPosition.column);
rginda8ba33642011-12-14 12:31:31 -08001685};
1686
1687/**
1688 * Return the cursor row.
1689 *
1690 * @return {integer} The zero-based cursor row.
1691 */
1692hterm.Terminal.prototype.getCursorRow = function(row) {
1693 return this.screen_.cursorPosition.row;
1694};
1695
1696/**
1697 * Request that the ScrollPort redraw itself soon.
1698 *
1699 * The redraw will happen asynchronously, soon after the call stack winds down.
1700 * Multiple calls will be coalesced into a single redraw.
1701 */
1702hterm.Terminal.prototype.scheduleRedraw_ = function() {
rginda87b86462011-12-14 13:48:03 -08001703 if (this.timeouts_.redraw)
1704 return;
rginda8ba33642011-12-14 12:31:31 -08001705
1706 var self = this;
rginda87b86462011-12-14 13:48:03 -08001707 this.timeouts_.redraw = setTimeout(function() {
1708 delete self.timeouts_.redraw;
rginda8ba33642011-12-14 12:31:31 -08001709 self.scrollPort_.redraw_();
1710 }, 0);
1711};
1712
1713/**
1714 * Request that the ScrollPort be scrolled to the bottom.
1715 *
1716 * The scroll will happen asynchronously, soon after the call stack winds down.
1717 * Multiple calls will be coalesced into a single scroll.
1718 *
1719 * This affects the scrollbar position of the ScrollPort, and has nothing to
1720 * do with the VT scroll commands.
1721 */
1722hterm.Terminal.prototype.scheduleScrollDown_ = function() {
1723 if (this.timeouts_.scrollDown)
rginda87b86462011-12-14 13:48:03 -08001724 return;
rginda8ba33642011-12-14 12:31:31 -08001725
1726 var self = this;
1727 this.timeouts_.scrollDown = setTimeout(function() {
1728 delete self.timeouts_.scrollDown;
1729 self.scrollPort_.scrollRowToBottom(self.getRowCount());
1730 }, 10);
1731};
1732
1733/**
1734 * Move the cursor up a specified number of rows.
1735 *
1736 * @param {integer} count The number of rows to move the cursor.
1737 */
1738hterm.Terminal.prototype.cursorUp = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001739 return this.cursorDown(-(count || 1));
rginda8ba33642011-12-14 12:31:31 -08001740};
1741
1742/**
1743 * Move the cursor down a specified number of rows.
1744 *
1745 * @param {integer} count The number of rows to move the cursor.
1746 */
1747hterm.Terminal.prototype.cursorDown = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001748 count = count || 1;
rginda8ba33642011-12-14 12:31:31 -08001749 var minHeight = (this.options_.originMode ? this.getVTScrollTop() : 0);
1750 var maxHeight = (this.options_.originMode ? this.getVTScrollBottom() :
1751 this.screenSize.height - 1);
1752
rgindacbbd7482012-06-13 15:06:16 -07001753 var row = lib.f.clamp(this.screen_.cursorPosition.row + count,
rginda8ba33642011-12-14 12:31:31 -08001754 minHeight, maxHeight);
rginda87b86462011-12-14 13:48:03 -08001755 this.setAbsoluteCursorRow(row);
rginda8ba33642011-12-14 12:31:31 -08001756};
1757
1758/**
1759 * Move the cursor left a specified number of columns.
1760 *
1761 * @param {integer} count The number of columns to move the cursor.
1762 */
1763hterm.Terminal.prototype.cursorLeft = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001764 return this.cursorRight(-(count || 1));
rginda8ba33642011-12-14 12:31:31 -08001765};
1766
1767/**
1768 * Move the cursor right a specified number of columns.
1769 *
1770 * @param {integer} count The number of columns to move the cursor.
1771 */
1772hterm.Terminal.prototype.cursorRight = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001773 count = count || 1;
rgindacbbd7482012-06-13 15:06:16 -07001774 var column = lib.f.clamp(this.screen_.cursorPosition.column + count,
rginda87b86462011-12-14 13:48:03 -08001775 0, this.screenSize.width - 1);
rginda8ba33642011-12-14 12:31:31 -08001776 this.setCursorColumn(column);
1777};
1778
1779/**
1780 * Reverse the foreground and background colors of the terminal.
1781 *
1782 * This only affects text that was drawn with no attributes.
1783 *
1784 * TODO(rginda): Test xterm to see if reverse is respected for text that has
1785 * been drawn with attributes that happen to coincide with the default
1786 * 'no-attribute' colors. My guess is probably not.
1787 */
1788hterm.Terminal.prototype.setReverseVideo = function(state) {
rginda87b86462011-12-14 13:48:03 -08001789 this.options_.reverseVideo = state;
rginda8ba33642011-12-14 12:31:31 -08001790 if (state) {
rginda9f5222b2012-03-05 11:53:28 -08001791 this.scrollPort_.setForegroundColor(this.prefs_.get('background-color'));
1792 this.scrollPort_.setBackgroundColor(this.prefs_.get('foreground-color'));
rginda8ba33642011-12-14 12:31:31 -08001793 } else {
rginda9f5222b2012-03-05 11:53:28 -08001794 this.scrollPort_.setForegroundColor(this.prefs_.get('foreground-color'));
1795 this.scrollPort_.setBackgroundColor(this.prefs_.get('background-color'));
rginda8ba33642011-12-14 12:31:31 -08001796 }
1797};
1798
1799/**
rginda87b86462011-12-14 13:48:03 -08001800 * Ring the terminal bell.
rginda87b86462011-12-14 13:48:03 -08001801 */
1802hterm.Terminal.prototype.ringBell = function() {
rginda9f5222b2012-03-05 11:53:28 -08001803 if (this.bellAudio_.getAttribute('src'))
1804 this.bellAudio_.play();
rgindaf0090c92012-02-10 14:58:52 -08001805
rginda6d397402012-01-17 10:58:29 -08001806 this.cursorNode_.style.backgroundColor =
1807 this.scrollPort_.getForegroundColor();
rginda87b86462011-12-14 13:48:03 -08001808
1809 var self = this;
1810 setTimeout(function() {
rginda9f5222b2012-03-05 11:53:28 -08001811 self.cursorNode_.style.backgroundColor = self.prefs_.get('cursor-color');
rginda6d397402012-01-17 10:58:29 -08001812 }, 200);
rginda87b86462011-12-14 13:48:03 -08001813};
1814
1815/**
rginda8ba33642011-12-14 12:31:31 -08001816 * Set the origin mode bit.
1817 *
1818 * If origin mode is on, certain VT cursor and scrolling commands measure their
1819 * row parameter relative to the VT scroll region. Otherwise, row 0 corresponds
1820 * to the top of the addressable screen.
1821 *
1822 * Defaults to off.
1823 *
1824 * @param {boolean} state True to set origin mode, false to unset.
1825 */
1826hterm.Terminal.prototype.setOriginMode = function(state) {
1827 this.options_.originMode = state;
rgindae4d29232012-01-19 10:47:13 -08001828 this.setCursorPosition(0, 0);
rginda8ba33642011-12-14 12:31:31 -08001829};
1830
1831/**
1832 * Set the insert mode bit.
1833 *
1834 * If insert mode is on, existing text beyond the cursor position will be
1835 * shifted right to make room for new text. Otherwise, new text overwrites
1836 * any existing text.
1837 *
1838 * Defaults to off.
1839 *
1840 * @param {boolean} state True to set insert mode, false to unset.
1841 */
1842hterm.Terminal.prototype.setInsertMode = function(state) {
1843 this.options_.insertMode = state;
1844};
1845
1846/**
rginda87b86462011-12-14 13:48:03 -08001847 * Set the auto carriage return bit.
1848 *
1849 * If auto carriage return is on then a formfeed character is interpreted
1850 * as a newline, otherwise it's the same as a linefeed. The difference boils
1851 * down to whether or not the cursor column is reset.
1852 */
1853hterm.Terminal.prototype.setAutoCarriageReturn = function(state) {
1854 this.options_.autoCarriageReturn = state;
1855};
1856
1857/**
rginda8ba33642011-12-14 12:31:31 -08001858 * Set the wraparound mode bit.
1859 *
1860 * If wraparound mode is on, certain VT commands will allow the cursor to wrap
1861 * to the start of the following row. Otherwise, the cursor is clamped to the
1862 * end of the screen and attempts to write past it are ignored.
1863 *
1864 * Defaults to on.
1865 *
1866 * @param {boolean} state True to set wraparound mode, false to unset.
1867 */
1868hterm.Terminal.prototype.setWraparound = function(state) {
1869 this.options_.wraparound = state;
1870};
1871
1872/**
1873 * Set the reverse-wraparound mode bit.
1874 *
1875 * If wraparound mode is off, certain VT commands will allow the cursor to wrap
1876 * to the end of the previous row. Otherwise, the cursor is clamped to column
1877 * 0.
1878 *
1879 * Defaults to off.
1880 *
1881 * @param {boolean} state True to set reverse-wraparound mode, false to unset.
1882 */
1883hterm.Terminal.prototype.setReverseWraparound = function(state) {
1884 this.options_.reverseWraparound = state;
1885};
1886
1887/**
1888 * Selects between the primary and alternate screens.
1889 *
1890 * If alternate mode is on, the alternate screen is active. Otherwise the
1891 * primary screen is active.
1892 *
1893 * Swapping screens has no effect on the scrollback buffer.
1894 *
1895 * Each screen maintains its own cursor position.
1896 *
1897 * Defaults to off.
1898 *
1899 * @param {boolean} state True to set alternate mode, false to unset.
1900 */
1901hterm.Terminal.prototype.setAlternateMode = function(state) {
rginda6d397402012-01-17 10:58:29 -08001902 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001903 this.screen_ = state ? this.alternateScreen_ : this.primaryScreen_;
1904
rginda35c456b2012-02-09 17:29:05 -08001905 if (this.screen_.rowsArray.length &&
1906 this.screen_.rowsArray[0].rowIndex != this.scrollbackRows_.length) {
1907 // If the screen changed sizes while we were away, our rowIndexes may
1908 // be incorrect.
1909 var offset = this.scrollbackRows_.length;
1910 var ary = this.screen_.rowsArray;
rgindacbbd7482012-06-13 15:06:16 -07001911 for (var i = 0; i < ary.length; i++) {
rginda35c456b2012-02-09 17:29:05 -08001912 ary[i].rowIndex = offset + i;
1913 }
1914 }
rginda8ba33642011-12-14 12:31:31 -08001915
rginda35c456b2012-02-09 17:29:05 -08001916 this.realizeWidth_(this.screenSize.width);
1917 this.realizeHeight_(this.screenSize.height);
1918 this.scrollPort_.syncScrollHeight();
1919 this.scrollPort_.invalidate();
rginda8ba33642011-12-14 12:31:31 -08001920
rginda6d397402012-01-17 10:58:29 -08001921 this.restoreCursor(cursor);
rginda35c456b2012-02-09 17:29:05 -08001922 this.scrollPort_.resize();
rginda8ba33642011-12-14 12:31:31 -08001923};
1924
1925/**
1926 * Set the cursor-blink mode bit.
1927 *
1928 * If cursor-blink is on, the cursor will blink when it is visible. Otherwise
1929 * a visible cursor does not blink.
1930 *
1931 * You should make sure to turn blinking off if you're going to dispose of a
1932 * terminal, otherwise you'll leak a timeout.
1933 *
1934 * Defaults to on.
1935 *
1936 * @param {boolean} state True to set cursor-blink mode, false to unset.
1937 */
1938hterm.Terminal.prototype.setCursorBlink = function(state) {
1939 this.options_.cursorBlink = state;
1940
1941 if (!state && this.timeouts_.cursorBlink) {
1942 clearTimeout(this.timeouts_.cursorBlink);
1943 delete this.timeouts_.cursorBlink;
1944 }
1945
1946 if (this.options_.cursorVisible)
1947 this.setCursorVisible(true);
1948};
1949
1950/**
1951 * Set the cursor-visible mode bit.
1952 *
1953 * If cursor-visible is on, the cursor will be visible. Otherwise it will not.
1954 *
1955 * Defaults to on.
1956 *
1957 * @param {boolean} state True to set cursor-visible mode, false to unset.
1958 */
1959hterm.Terminal.prototype.setCursorVisible = function(state) {
1960 this.options_.cursorVisible = state;
1961
1962 if (!state) {
rginda87b86462011-12-14 13:48:03 -08001963 this.cursorNode_.style.opacity = '0';
rginda8ba33642011-12-14 12:31:31 -08001964 return;
1965 }
1966
rginda87b86462011-12-14 13:48:03 -08001967 this.syncCursorPosition_();
1968
1969 this.cursorNode_.style.opacity = '1';
rginda8ba33642011-12-14 12:31:31 -08001970
1971 if (this.options_.cursorBlink) {
1972 if (this.timeouts_.cursorBlink)
1973 return;
1974
1975 this.timeouts_.cursorBlink = setInterval(this.onCursorBlink_.bind(this),
1976 500);
1977 } else {
1978 if (this.timeouts_.cursorBlink) {
1979 clearTimeout(this.timeouts_.cursorBlink);
1980 delete this.timeouts_.cursorBlink;
1981 }
1982 }
1983};
1984
1985/**
rginda87b86462011-12-14 13:48:03 -08001986 * Synchronizes the visible cursor and document selection with the current
1987 * cursor coordinates.
rginda8ba33642011-12-14 12:31:31 -08001988 */
1989hterm.Terminal.prototype.syncCursorPosition_ = function() {
1990 var topRowIndex = this.scrollPort_.getTopRowIndex();
1991 var bottomRowIndex = this.scrollPort_.getBottomRowIndex(topRowIndex);
1992 var cursorRowIndex = this.scrollbackRows_.length +
1993 this.screen_.cursorPosition.row;
1994
1995 if (cursorRowIndex > bottomRowIndex) {
1996 // Cursor is scrolled off screen, move it outside of the visible area.
rginda35c456b2012-02-09 17:29:05 -08001997 this.cursorNode_.style.top = -this.scrollPort_.characterSize.height + 'px';
rginda8ba33642011-12-14 12:31:31 -08001998 return;
1999 }
2000
rginda35c456b2012-02-09 17:29:05 -08002001 this.cursorNode_.style.width = this.scrollPort_.characterSize.width + 'px';
2002 this.cursorNode_.style.height = this.scrollPort_.characterSize.height + 'px';
2003
rginda8ba33642011-12-14 12:31:31 -08002004 this.cursorNode_.style.top = this.scrollPort_.visibleRowTopMargin +
rginda35c456b2012-02-09 17:29:05 -08002005 this.scrollPort_.characterSize.height * (cursorRowIndex - topRowIndex) +
2006 'px';
2007 this.cursorNode_.style.left = this.scrollPort_.characterSize.width *
2008 this.screen_.cursorPosition.column + 'px';
rginda87b86462011-12-14 13:48:03 -08002009
2010 this.cursorNode_.setAttribute('title',
2011 '(' + this.screen_.cursorPosition.row +
2012 ', ' + this.screen_.cursorPosition.column +
2013 ')');
2014
2015 // Update the caret for a11y purposes.
2016 var selection = this.document_.getSelection();
2017 if (selection && selection.isCollapsed)
2018 this.screen_.syncSelectionCaret(selection);
rginda8ba33642011-12-14 12:31:31 -08002019};
2020
2021/**
2022 * Synchronizes the visible cursor with the current cursor coordinates.
2023 *
2024 * The sync will happen asynchronously, soon after the call stack winds down.
2025 * Multiple calls will be coalesced into a single sync.
2026 */
2027hterm.Terminal.prototype.scheduleSyncCursorPosition_ = function() {
2028 if (this.timeouts_.syncCursor)
rginda87b86462011-12-14 13:48:03 -08002029 return;
rginda8ba33642011-12-14 12:31:31 -08002030
2031 var self = this;
2032 this.timeouts_.syncCursor = setTimeout(function() {
2033 self.syncCursorPosition_();
2034 delete self.timeouts_.syncCursor;
rginda87b86462011-12-14 13:48:03 -08002035 }, 0);
2036};
2037
rgindacc2996c2012-02-24 14:59:31 -08002038/**
rgindaf522ce02012-04-17 17:49:17 -07002039 * Show or hide the zoom warning.
2040 *
2041 * The zoom warning is a message warning the user that their browser zoom must
2042 * be set to 100% in order for hterm to function properly.
2043 *
2044 * @param {boolean} state True to show the message, false to hide it.
2045 */
2046hterm.Terminal.prototype.showZoomWarning_ = function(state) {
2047 if (!this.zoomWarningNode_) {
2048 if (!state)
2049 return;
2050
2051 this.zoomWarningNode_ = this.document_.createElement('div');
2052 this.zoomWarningNode_.style.cssText = (
2053 'color: black;' +
2054 'background-color: #ff2222;' +
2055 'font-size: large;' +
2056 'border-radius: 8px;' +
2057 'opacity: 0.75;' +
2058 'padding: 0.2em 0.5em 0.2em 0.5em;' +
2059 'top: 0.5em;' +
2060 'right: 1.2em;' +
2061 'position: absolute;' +
2062 '-webkit-text-size-adjust: none;' +
2063 '-webkit-user-select: none;');
rgindaf522ce02012-04-17 17:49:17 -07002064 }
2065
rgindade84e382012-04-20 15:39:31 -07002066 this.zoomWarningNode_.textContent = hterm.msg('ZOOM_WARNING') ||
2067 ('!! ' + parseInt(this.scrollPort_.characterSize.zoomFactor * 100) +
2068 '% !!');
rgindaf522ce02012-04-17 17:49:17 -07002069 this.zoomWarningNode_.style.fontFamily = this.prefs_.get('font-family');
2070
2071 if (state) {
2072 if (!this.zoomWarningNode_.parentNode)
2073 this.div_.parentNode.appendChild(this.zoomWarningNode_);
2074 } else if (this.zoomWarningNode_.parentNode) {
2075 this.zoomWarningNode_.parentNode.removeChild(this.zoomWarningNode_);
2076 }
2077};
2078
2079/**
rgindacc2996c2012-02-24 14:59:31 -08002080 * Show the terminal overlay for a given amount of time.
2081 *
2082 * The terminal overlay appears in inverse video in a large font, centered
2083 * over the terminal. You should probably keep the overlay message brief,
2084 * since it's in a large font and you probably aren't going to check the size
2085 * of the terminal first.
2086 *
2087 * @param {string} msg The text (not HTML) message to display in the overlay.
2088 * @param {number} opt_timeout The amount of time to wait before fading out
2089 * the overlay. Defaults to 1.5 seconds. Pass null to have the overlay
2090 * stay up forever (or until the next overlay).
2091 */
2092hterm.Terminal.prototype.showOverlay = function(msg, opt_timeout) {
rgindaf0090c92012-02-10 14:58:52 -08002093 if (!this.overlayNode_) {
2094 if (!this.div_)
2095 return;
2096
2097 this.overlayNode_ = this.document_.createElement('div');
2098 this.overlayNode_.style.cssText = (
rgindaf0090c92012-02-10 14:58:52 -08002099 'border-radius: 15px;' +
rgindaf0090c92012-02-10 14:58:52 -08002100 'font-size: xx-large;' +
2101 'opacity: 0.75;' +
2102 'padding: 0.2em 0.5em 0.2em 0.5em;' +
2103 'position: absolute;' +
2104 '-webkit-user-select: none;' +
2105 '-webkit-transition: opacity 180ms ease-in;');
2106 }
2107
rginda9f5222b2012-03-05 11:53:28 -08002108 this.overlayNode_.style.color = this.prefs_.get('background-color');
2109 this.overlayNode_.style.backgroundColor = this.prefs_.get('foreground-color');
2110 this.overlayNode_.style.fontFamily = this.prefs_.get('font-family');
2111
rgindaf0090c92012-02-10 14:58:52 -08002112 this.overlayNode_.textContent = msg;
2113 this.overlayNode_.style.opacity = '0.75';
2114
2115 if (!this.overlayNode_.parentNode)
2116 this.div_.appendChild(this.overlayNode_);
2117
2118 this.overlayNode_.style.top = (
2119 this.div_.clientHeight - this.overlayNode_.clientHeight) / 2;
2120 this.overlayNode_.style.left = (
2121 this.div_.clientWidth - this.overlayNode_.clientWidth -
2122 this.scrollbarWidthPx) / 2;
2123
2124 var self = this;
2125
2126 if (this.overlayTimeout_)
2127 clearTimeout(this.overlayTimeout_);
2128
rgindacc2996c2012-02-24 14:59:31 -08002129 if (opt_timeout === null)
2130 return;
2131
rgindaf0090c92012-02-10 14:58:52 -08002132 this.overlayTimeout_ = setTimeout(function() {
2133 self.overlayNode_.style.opacity = '0';
2134 setTimeout(function() {
rginda259dcca2012-03-14 16:37:11 -07002135 if (self.overlayNode_.parentNode)
2136 self.overlayNode_.parentNode.removeChild(self.overlayNode_);
rgindaf0090c92012-02-10 14:58:52 -08002137 self.overlayTimeout_ = null;
2138 self.overlayNode_.style.opacity = '0.75';
2139 }, 200);
rgindacc2996c2012-02-24 14:59:31 -08002140 }, opt_timeout || 1500);
rgindaf0090c92012-02-10 14:58:52 -08002141};
2142
2143hterm.Terminal.prototype.overlaySize = function() {
2144 this.showOverlay(this.screenSize.width + 'x' + this.screenSize.height);
2145};
2146
rginda87b86462011-12-14 13:48:03 -08002147/**
2148 * Invoked by hterm.Terminal.Keyboard when a VT keystroke is detected.
2149 *
2150 * @param {string} string The VT string representing the keystroke.
2151 */
2152hterm.Terminal.prototype.onVTKeystroke = function(string) {
rginda9f5222b2012-03-05 11:53:28 -08002153 if (this.scrollOnKeystroke_)
rginda87b86462011-12-14 13:48:03 -08002154 this.scrollPort_.scrollRowToBottom(this.getRowCount());
2155
2156 this.io.onVTKeystroke(string);
rginda8ba33642011-12-14 12:31:31 -08002157};
2158
2159/**
rginda8e92a692012-05-20 19:37:20 -07002160 * React when focus changes.
2161 */
2162hterm.Terminal.prototype.onFocusChange_ = function(state) {
2163 this.cursorNode_.setAttribute('focus', state ? 'true' : 'false');
2164};
2165
2166/**
rginda8ba33642011-12-14 12:31:31 -08002167 * React when the ScrollPort is scrolled.
2168 */
2169hterm.Terminal.prototype.onScroll_ = function() {
2170 this.scheduleSyncCursorPosition_();
2171};
2172
2173/**
rginda9846e2f2012-01-27 13:53:33 -08002174 * React when text is pasted into the scrollPort.
2175 */
2176hterm.Terminal.prototype.onPaste_ = function(e) {
2177 this.io.onVTKeystroke(e.text);
2178};
2179
2180/**
rginda8ba33642011-12-14 12:31:31 -08002181 * React when the ScrollPort is resized.
rgindac9bc5502012-01-18 11:48:44 -08002182 *
2183 * Note: This function should not directly contain code that alters the internal
2184 * state of the terminal. That kind of code belongs in realizeWidth or
2185 * realizeHeight, so that it can be executed synchronously in the case of a
2186 * programmatic width change.
rginda8ba33642011-12-14 12:31:31 -08002187 */
2188hterm.Terminal.prototype.onResize_ = function() {
rgindac9bc5502012-01-18 11:48:44 -08002189 var columnCount = Math.floor(this.scrollPort_.getScreenWidth() /
rginda35c456b2012-02-09 17:29:05 -08002190 this.scrollPort_.characterSize.width);
2191 var rowCount = Math.floor(this.scrollPort_.getScreenHeight() /
2192 this.scrollPort_.characterSize.height);
2193
2194 if (!(columnCount || rowCount)) {
2195 // We avoid these situations since they happen sometimes when the terminal
2196 // gets removed from the document, and we can't deal with that.
2197 return;
2198 }
2199
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +04002200 this.realizeSize_(columnCount, rowCount);
rgindac9bc5502012-01-18 11:48:44 -08002201 this.scheduleSyncCursorPosition_();
rgindaf522ce02012-04-17 17:49:17 -07002202 this.showZoomWarning_(this.scrollPort_.characterSize.zoomFactor != 1);
rgindaf0090c92012-02-10 14:58:52 -08002203 this.overlaySize();
rginda8ba33642011-12-14 12:31:31 -08002204};
2205
2206/**
2207 * Service the cursor blink timeout.
2208 */
2209hterm.Terminal.prototype.onCursorBlink_ = function() {
rginda87b86462011-12-14 13:48:03 -08002210 if (this.cursorNode_.style.opacity == '0') {
2211 this.cursorNode_.style.opacity = '1';
rginda8ba33642011-12-14 12:31:31 -08002212 } else {
rginda87b86462011-12-14 13:48:03 -08002213 this.cursorNode_.style.opacity = '0';
rginda8ba33642011-12-14 12:31:31 -08002214 }
2215};
David Reveman8f552492012-03-28 12:18:41 -04002216
2217/**
2218 * Set the scrollbar-visible mode bit.
2219 *
2220 * If scrollbar-visible is on, the vertical scrollbar will be visible.
2221 * Otherwise it will not.
2222 *
2223 * Defaults to on.
2224 *
2225 * @param {boolean} state True to set scrollbar-visible mode, false to unset.
2226 */
2227hterm.Terminal.prototype.setScrollbarVisible = function(state) {
2228 this.scrollPort_.setScrollbarVisible(state);
2229};