blob: 74eaf9c9ff03981314e37e9cdf301d3fc3c6076e [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
rginda1a09aa02012-06-18 21:11:25 -0700555hterm.Terminal.prototype.setTextAttributes = function(textAttributes) {
556 this.screen_.textAttributes = textAttributes;
557};
558
rginda87b86462011-12-14 13:48:03 -0800559/**
rgindaf522ce02012-04-17 17:49:17 -0700560 * Return the current browser zoom factor applied to the terminal.
561 *
562 * @return {number} The current browser zoom factor.
563 */
564hterm.Terminal.prototype.getZoomFactor = function() {
565 return this.scrollPort_.characterSize.zoomFactor;
566};
567
568/**
rginda9846e2f2012-01-27 13:53:33 -0800569 * Change the title of this terminal's window.
570 */
571hterm.Terminal.prototype.setWindowTitle = function(title) {
rgindafeaf3142012-01-31 15:14:20 -0800572 window.document.title = title;
rginda9846e2f2012-01-27 13:53:33 -0800573};
574
575/**
rginda87b86462011-12-14 13:48:03 -0800576 * Restore a previously saved cursor position.
577 *
578 * @param {hterm.RowCol} cursor The position to restore.
579 */
580hterm.Terminal.prototype.restoreCursor = function(cursor) {
rgindacbbd7482012-06-13 15:06:16 -0700581 var row = lib.f.clamp(cursor.row, 0, this.screenSize.height - 1);
582 var column = lib.f.clamp(cursor.column, 0, this.screenSize.width - 1);
rginda35c456b2012-02-09 17:29:05 -0800583 this.screen_.setCursorPosition(row, column);
584 if (cursor.column > column ||
585 cursor.column == column && cursor.overflow) {
586 this.screen_.cursorPosition.overflow = true;
587 }
rginda87b86462011-12-14 13:48:03 -0800588};
589
590/**
David Benjamin54e8bf62012-06-01 22:31:40 -0400591 * Clear the cursor's overflow flag.
592 */
593hterm.Terminal.prototype.clearCursorOverflow = function() {
594 this.screen_.cursorPosition.overflow = false;
595};
596
597/**
rginda87b86462011-12-14 13:48:03 -0800598 * Set the width of the terminal, resizing the UI to match.
599 */
600hterm.Terminal.prototype.setWidth = function(columnCount) {
rgindaf0090c92012-02-10 14:58:52 -0800601 if (columnCount == null) {
602 this.div_.style.width = '100%';
603 return;
604 }
605
rginda35c456b2012-02-09 17:29:05 -0800606 this.div_.style.width = this.scrollPort_.characterSize.width *
607 columnCount + this.scrollbarWidthPx + 'px';
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +0400608 this.realizeSize_(columnCount, this.screenSize.height);
rgindac9bc5502012-01-18 11:48:44 -0800609 this.scheduleSyncCursorPosition_();
610};
rginda87b86462011-12-14 13:48:03 -0800611
rgindac9bc5502012-01-18 11:48:44 -0800612/**
rginda35c456b2012-02-09 17:29:05 -0800613 * Set the height of the terminal, resizing the UI to match.
614 */
615hterm.Terminal.prototype.setHeight = function(rowCount) {
rgindaf0090c92012-02-10 14:58:52 -0800616 if (rowCount == null) {
617 this.div_.style.height = '100%';
618 return;
619 }
620
rginda35c456b2012-02-09 17:29:05 -0800621 this.div_.style.height =
rginda30f20f62012-04-05 16:36:19 -0700622 this.scrollPort_.characterSize.height * rowCount + 'px';
rginda35c456b2012-02-09 17:29:05 -0800623 this.realizeSize_(this.screenSize.width, rowCount);
624 this.scheduleSyncCursorPosition_();
625};
626
627/**
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +0400628 * Deal with terminal size changes.
629 *
630 */
631hterm.Terminal.prototype.realizeSize_ = function(columnCount, rowCount) {
632 if (columnCount != this.screenSize.width)
633 this.realizeWidth_(columnCount);
634
635 if (rowCount != this.screenSize.height)
636 this.realizeHeight_(rowCount);
637
638 // Send new terminal size to plugin.
639 this.io.onTerminalResize(columnCount, rowCount);
640};
641
642/**
rgindac9bc5502012-01-18 11:48:44 -0800643 * Deal with terminal width changes.
644 *
645 * This function does what needs to be done when the terminal width changes
646 * out from under us. It happens here rather than in onResize_() because this
647 * code may need to run synchronously to handle programmatic changes of
648 * terminal width.
649 *
650 * Relying on the browser to send us an async resize event means we may not be
651 * in the correct state yet when the next escape sequence hits.
652 */
653hterm.Terminal.prototype.realizeWidth_ = function(columnCount) {
654 var deltaColumns = columnCount - this.screen_.getWidth();
655
rginda87b86462011-12-14 13:48:03 -0800656 this.screenSize.width = columnCount;
657 this.screen_.setColumnCount(columnCount);
rgindac9bc5502012-01-18 11:48:44 -0800658
659 if (deltaColumns > 0) {
David Benjamin66e954d2012-05-05 21:08:12 -0400660 if (this.defaultTabStops)
661 this.setDefaultTabStops(this.screenSize.width - deltaColumns);
rgindac9bc5502012-01-18 11:48:44 -0800662 } else {
663 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
David Benjamin66e954d2012-05-05 21:08:12 -0400664 if (this.tabStops_[i] < columnCount)
rgindac9bc5502012-01-18 11:48:44 -0800665 break;
666
667 this.tabStops_.pop();
668 }
669 }
670
671 this.screen_.setColumnCount(this.screenSize.width);
672};
673
674/**
675 * Deal with terminal height changes.
676 *
677 * This function does what needs to be done when the terminal height changes
678 * out from under us. It happens here rather than in onResize_() because this
679 * code may need to run synchronously to handle programmatic changes of
680 * terminal height.
681 *
682 * Relying on the browser to send us an async resize event means we may not be
683 * in the correct state yet when the next escape sequence hits.
684 */
685hterm.Terminal.prototype.realizeHeight_ = function(rowCount) {
686 var deltaRows = rowCount - this.screen_.getHeight();
687
688 this.screenSize.height = rowCount;
689
690 var cursor = this.saveCursor();
691
692 if (deltaRows < 0) {
693 // Screen got smaller.
694 deltaRows *= -1;
695 while (deltaRows) {
696 var lastRow = this.getRowCount() - 1;
697 if (lastRow - this.scrollbackRows_.length == cursor.row)
698 break;
699
700 if (this.getRowText(lastRow))
701 break;
702
703 this.screen_.popRow();
704 deltaRows--;
705 }
706
707 var ary = this.screen_.shiftRows(deltaRows);
708 this.scrollbackRows_.push.apply(this.scrollbackRows_, ary);
709
710 // We just removed rows from the top of the screen, we need to update
711 // the cursor to match.
rginda35c456b2012-02-09 17:29:05 -0800712 cursor.row = Math.max(cursor.row - deltaRows, 0);
rgindac9bc5502012-01-18 11:48:44 -0800713 } else if (deltaRows > 0) {
714 // Screen got larger.
715
716 if (deltaRows <= this.scrollbackRows_.length) {
717 var scrollbackCount = Math.min(deltaRows, this.scrollbackRows_.length);
718 var rows = this.scrollbackRows_.splice(
719 this.scrollbackRows_.length - scrollbackCount, scrollbackCount);
720 this.screen_.unshiftRows(rows);
721 deltaRows -= scrollbackCount;
722 cursor.row += scrollbackCount;
723 }
724
725 if (deltaRows)
726 this.appendRows_(deltaRows);
727 }
728
rginda35c456b2012-02-09 17:29:05 -0800729 this.setVTScrollRegion(null, null);
rgindac9bc5502012-01-18 11:48:44 -0800730 this.restoreCursor(cursor);
rginda87b86462011-12-14 13:48:03 -0800731};
732
733/**
734 * Scroll the terminal to the top of the scrollback buffer.
735 */
736hterm.Terminal.prototype.scrollHome = function() {
737 this.scrollPort_.scrollRowToTop(0);
738};
739
740/**
741 * Scroll the terminal to the end.
742 */
743hterm.Terminal.prototype.scrollEnd = function() {
744 this.scrollPort_.scrollRowToBottom(this.getRowCount());
745};
746
747/**
748 * Scroll the terminal one page up (minus one line) relative to the current
749 * position.
750 */
751hterm.Terminal.prototype.scrollPageUp = function() {
752 var i = this.scrollPort_.getTopRowIndex();
753 this.scrollPort_.scrollRowToTop(i - this.screenSize.height + 1);
754};
755
756/**
757 * Scroll the terminal one page down (minus one line) relative to the current
758 * position.
759 */
760hterm.Terminal.prototype.scrollPageDown = function() {
761 var i = this.scrollPort_.getTopRowIndex();
762 this.scrollPort_.scrollRowToTop(i + this.screenSize.height - 1);
rginda8ba33642011-12-14 12:31:31 -0800763};
764
rgindac9bc5502012-01-18 11:48:44 -0800765/**
766 * Full terminal reset.
767 */
rginda87b86462011-12-14 13:48:03 -0800768hterm.Terminal.prototype.reset = function() {
rgindac9bc5502012-01-18 11:48:44 -0800769 this.clearAllTabStops();
770 this.setDefaultTabStops();
rginda9ea433c2012-03-16 11:57:00 -0700771
772 this.clearHome(this.primaryScreen_);
773 this.primaryScreen_.textAttributes.reset();
774
775 this.clearHome(this.alternateScreen_);
776 this.alternateScreen_.textAttributes.reset();
777
rgindab8bc8932012-04-27 12:45:03 -0700778 this.setCursorBlink(!!this.prefs_.get('cursor-blink'));
779
rgindac9bc5502012-01-18 11:48:44 -0800780 this.softReset();
rginda87b86462011-12-14 13:48:03 -0800781};
782
rgindac9bc5502012-01-18 11:48:44 -0800783/**
784 * Soft terminal reset.
rgindab8bc8932012-04-27 12:45:03 -0700785 *
786 * Perform a soft reset to the default values listed in
787 * http://www.vt100.net/docs/vt510-rm/DECSTR#T5-9
rgindac9bc5502012-01-18 11:48:44 -0800788 */
rginda0f5c0292012-01-13 11:00:13 -0800789hterm.Terminal.prototype.softReset = function() {
rgindab8bc8932012-04-27 12:45:03 -0700790 // Reset terminal options to their default values.
rgindac9bc5502012-01-18 11:48:44 -0800791 this.options_ = new hterm.Options();
rgindaf522ce02012-04-17 17:49:17 -0700792
rgindab8bc8932012-04-27 12:45:03 -0700793 // Xterm also resets the color palette on soft reset, even though it doesn't
794 // seem to be documented anywhere.
rgindaf522ce02012-04-17 17:49:17 -0700795 this.primaryScreen_.textAttributes.resetColorPalette();
796 this.alternateScreen_.textAttributes.resetColorPalette();
797
rgindab8bc8932012-04-27 12:45:03 -0700798 // The xterm man page explicitly says this will happen on soft reset.
799 this.setVTScrollRegion(null, null);
800
801 // Xterm also shows the cursor on soft reset, but does not alter the blink
802 // state.
rgindaa19afe22012-01-25 15:40:22 -0800803 this.setCursorVisible(true);
rginda0f5c0292012-01-13 11:00:13 -0800804};
805
rgindac9bc5502012-01-18 11:48:44 -0800806/**
807 * Move the cursor forward to the next tab stop, or to the last column
808 * if no more tab stops are set.
809 */
810hterm.Terminal.prototype.forwardTabStop = function() {
811 var column = this.screen_.cursorPosition.column;
812
813 for (var i = 0; i < this.tabStops_.length; i++) {
814 if (this.tabStops_[i] > column) {
815 this.setCursorColumn(this.tabStops_[i]);
816 return;
817 }
818 }
819
David Benjamin66e954d2012-05-05 21:08:12 -0400820 // xterm does not clear the overflow flag on HT or CHT.
821 var overflow = this.screen_.cursorPosition.overflow;
rgindac9bc5502012-01-18 11:48:44 -0800822 this.setCursorColumn(this.screenSize.width - 1);
David Benjamin66e954d2012-05-05 21:08:12 -0400823 this.screen_.cursorPosition.overflow = overflow;
rginda0f5c0292012-01-13 11:00:13 -0800824};
825
rgindac9bc5502012-01-18 11:48:44 -0800826/**
827 * Move the cursor backward to the previous tab stop, or to the first column
828 * if no previous tab stops are set.
829 */
830hterm.Terminal.prototype.backwardTabStop = function() {
831 var column = this.screen_.cursorPosition.column;
832
833 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
834 if (this.tabStops_[i] < column) {
835 this.setCursorColumn(this.tabStops_[i]);
836 return;
837 }
838 }
839
840 this.setCursorColumn(1);
rginda0f5c0292012-01-13 11:00:13 -0800841};
842
rgindac9bc5502012-01-18 11:48:44 -0800843/**
844 * Set a tab stop at the given column.
845 *
846 * @param {int} column Zero based column.
847 */
848hterm.Terminal.prototype.setTabStop = function(column) {
849 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
850 if (this.tabStops_[i] == column)
851 return;
852
853 if (this.tabStops_[i] < column) {
854 this.tabStops_.splice(i + 1, 0, column);
855 return;
856 }
857 }
858
859 this.tabStops_.splice(0, 0, column);
rginda87b86462011-12-14 13:48:03 -0800860};
861
rgindac9bc5502012-01-18 11:48:44 -0800862/**
863 * Clear the tab stop at the current cursor position.
864 *
865 * No effect if there is no tab stop at the current cursor position.
866 */
867hterm.Terminal.prototype.clearTabStopAtCursor = function() {
868 var column = this.screen_.cursorPosition.column;
869
870 var i = this.tabStops_.indexOf(column);
871 if (i == -1)
872 return;
873
874 this.tabStops_.splice(i, 1);
875};
876
877/**
878 * Clear all tab stops.
879 */
880hterm.Terminal.prototype.clearAllTabStops = function() {
881 this.tabStops_.length = 0;
David Benjamin66e954d2012-05-05 21:08:12 -0400882 this.defaultTabStops = false;
rgindac9bc5502012-01-18 11:48:44 -0800883};
884
885/**
886 * Set up the default tab stops, starting from a given column.
887 *
888 * This sets a tabstop every (column % this.tabWidth) column, starting
David Benjamin66e954d2012-05-05 21:08:12 -0400889 * from the specified column, or 0 if no column is provided. It also flags
890 * future resizes to set them up.
rgindac9bc5502012-01-18 11:48:44 -0800891 *
892 * This does not clear the existing tab stops first, use clearAllTabStops
893 * for that.
894 *
895 * @param {int} opt_start Optional starting zero based starting column, useful
896 * for filling out missing tab stops when the terminal is resized.
897 */
898hterm.Terminal.prototype.setDefaultTabStops = function(opt_start) {
899 var start = opt_start || 0;
900 var w = this.tabWidth;
David Benjamin66e954d2012-05-05 21:08:12 -0400901 // Round start up to a default tab stop.
902 start = start - 1 - ((start - 1) % w) + w;
903 for (var i = start; i < this.screenSize.width; i += w) {
904 this.setTabStop(i);
rgindac9bc5502012-01-18 11:48:44 -0800905 }
David Benjamin66e954d2012-05-05 21:08:12 -0400906
907 this.defaultTabStops = true;
rginda87b86462011-12-14 13:48:03 -0800908};
909
rginda6d397402012-01-17 10:58:29 -0800910/**
rginda8ba33642011-12-14 12:31:31 -0800911 * Interpret a sequence of characters.
912 *
913 * Incomplete escape sequences are buffered until the next call.
914 *
915 * @param {string} str Sequence of characters to interpret or pass through.
916 */
917hterm.Terminal.prototype.interpret = function(str) {
rginda0f5c0292012-01-13 11:00:13 -0800918 this.vt.interpret(str);
rginda8ba33642011-12-14 12:31:31 -0800919 this.scheduleSyncCursorPosition_();
920};
921
922/**
923 * Take over the given DIV for use as the terminal display.
924 *
925 * @param {HTMLDivElement} div The div to use as the terminal display.
926 */
927hterm.Terminal.prototype.decorate = function(div) {
rginda87b86462011-12-14 13:48:03 -0800928 this.div_ = div;
929
rginda8ba33642011-12-14 12:31:31 -0800930 this.scrollPort_.decorate(div);
rginda30f20f62012-04-05 16:36:19 -0700931 this.scrollPort_.setBackgroundImage(this.prefs_.get('background-image'));
Philip Douglass959b49d2012-05-30 13:29:29 -0400932 this.scrollPort_.setBackgroundSize(this.prefs_.get('background-size'));
933 this.scrollPort_.setBackgroundPosition(
934 this.prefs_.get('background-position'));
rginda30f20f62012-04-05 16:36:19 -0700935
rginda0918b652012-04-04 11:26:24 -0700936 this.div_.focus = this.focus.bind(this);
rgindaf7521392012-02-28 17:20:34 -0800937
rginda9f5222b2012-03-05 11:53:28 -0800938 this.setFontSize(this.prefs_.get('font-size'));
939 this.syncFontFamily();
rgindaa19afe22012-01-25 15:40:22 -0800940
David Reveman8f552492012-03-28 12:18:41 -0400941 this.setScrollbarVisible(this.prefs_.get('scrollbar-visible'));
942
rginda8ba33642011-12-14 12:31:31 -0800943 this.document_ = this.scrollPort_.getDocument();
944
rginda8e92a692012-05-20 19:37:20 -0700945 this.document_.body.firstChild.addEventListener(
946 'focus', this.onFocusChange_.bind(this, true));
947 this.document_.body.firstChild.addEventListener(
948 'blur', this.onFocusChange_.bind(this, false));
949
950 var style = this.document_.createElement('style');
951 style.textContent =
952 ('.cursor-node[focus="false"] {' +
953 ' box-sizing: border-box;' +
954 ' background-color: transparent !important;' +
955 ' border-width: 2px;' +
956 ' border-style: solid;' +
957 '}');
958 this.document_.head.appendChild(style);
959
rginda8ba33642011-12-14 12:31:31 -0800960 this.cursorNode_ = this.document_.createElement('div');
rginda8e92a692012-05-20 19:37:20 -0700961 this.cursorNode_.className = 'cursor-node';
rginda8ba33642011-12-14 12:31:31 -0800962 this.cursorNode_.style.cssText =
963 ('position: absolute;' +
rginda87b86462011-12-14 13:48:03 -0800964 'top: -99px;' +
965 'display: block;' +
rginda35c456b2012-02-09 17:29:05 -0800966 'width: ' + this.scrollPort_.characterSize.width + 'px;' +
967 'height: ' + this.scrollPort_.characterSize.height + 'px;' +
rginda8e92a692012-05-20 19:37:20 -0700968 '-webkit-transition: opacity, background-color 100ms linear;');
969 this.setCursorColor(this.prefs_.get('cursor-color'));
rginda8ba33642011-12-14 12:31:31 -0800970 this.document_.body.appendChild(this.cursorNode_);
971
rgindade84e382012-04-20 15:39:31 -0700972 this.setCursorBlink(!!this.prefs_.get('cursor-blink'));
rginda8ba33642011-12-14 12:31:31 -0800973 this.setReverseVideo(false);
rginda87b86462011-12-14 13:48:03 -0800974
rginda87b86462011-12-14 13:48:03 -0800975 this.scrollPort_.focus();
rginda6d397402012-01-17 10:58:29 -0800976 this.scrollPort_.scheduleRedraw();
rginda87b86462011-12-14 13:48:03 -0800977};
978
rginda0918b652012-04-04 11:26:24 -0700979/**
980 * Return the HTML document that contains the terminal DOM nodes.
981 */
rginda87b86462011-12-14 13:48:03 -0800982hterm.Terminal.prototype.getDocument = function() {
983 return this.document_;
rginda8ba33642011-12-14 12:31:31 -0800984};
985
986/**
rginda0918b652012-04-04 11:26:24 -0700987 * Focus the terminal.
988 */
989hterm.Terminal.prototype.focus = function() {
990 this.scrollPort_.focus();
991};
992
993/**
rginda8ba33642011-12-14 12:31:31 -0800994 * Return the HTML Element for a given row index.
995 *
996 * This is a method from the RowProvider interface. The ScrollPort uses
997 * it to fetch rows on demand as they are scrolled into view.
998 *
999 * TODO(rginda): Consider saving scrollback rows as (HTML source, text content)
1000 * pairs to conserve memory.
1001 *
1002 * @param {integer} index The zero-based row index, measured relative to the
1003 * start of the scrollback buffer. On-screen rows will always have the
1004 * largest indicies.
1005 * @return {HTMLElement} The 'x-row' element containing for the requested row.
1006 */
1007hterm.Terminal.prototype.getRowNode = function(index) {
1008 if (index < this.scrollbackRows_.length)
1009 return this.scrollbackRows_[index];
1010
1011 var screenIndex = index - this.scrollbackRows_.length;
1012 return this.screen_.rowsArray[screenIndex];
1013};
1014
1015/**
1016 * Return the text content for a given range of rows.
1017 *
1018 * This is a method from the RowProvider interface. The ScrollPort uses
1019 * it to fetch text content on demand when the user attempts to copy their
1020 * selection to the clipboard.
1021 *
1022 * @param {integer} start The zero-based row index to start from, measured
1023 * relative to the start of the scrollback buffer. On-screen rows will
1024 * always have the largest indicies.
1025 * @param {integer} end The zero-based row index to end on, measured
1026 * relative to the start of the scrollback buffer.
1027 * @return {string} A single string containing the text value of the range of
1028 * rows. Lines will be newline delimited, with no trailing newline.
1029 */
1030hterm.Terminal.prototype.getRowsText = function(start, end) {
1031 var ary = [];
1032 for (var i = start; i < end; i++) {
1033 var node = this.getRowNode(i);
1034 ary.push(node.textContent);
1035 }
1036
1037 return ary.join('\n');
1038};
1039
1040/**
1041 * Return the text content for a given row.
1042 *
1043 * This is a method from the RowProvider interface. The ScrollPort uses
1044 * it to fetch text content on demand when the user attempts to copy their
1045 * selection to the clipboard.
1046 *
1047 * @param {integer} index The zero-based row index to return, measured
1048 * relative to the start of the scrollback buffer. On-screen rows will
1049 * always have the largest indicies.
1050 * @return {string} A string containing the text value of the selected row.
1051 */
1052hterm.Terminal.prototype.getRowText = function(index) {
1053 var node = this.getRowNode(index);
rginda87b86462011-12-14 13:48:03 -08001054 return node.textContent;
rginda8ba33642011-12-14 12:31:31 -08001055};
1056
1057/**
1058 * Return the total number of rows in the addressable screen and in the
1059 * scrollback buffer of this terminal.
1060 *
1061 * This is a method from the RowProvider interface. The ScrollPort uses
1062 * it to compute the size of the scrollbar.
1063 *
1064 * @return {integer} The number of rows in this terminal.
1065 */
1066hterm.Terminal.prototype.getRowCount = function() {
1067 return this.scrollbackRows_.length + this.screen_.rowsArray.length;
1068};
1069
1070/**
1071 * Create DOM nodes for new rows and append them to the end of the terminal.
1072 *
1073 * This is the only correct way to add a new DOM node for a row. Notice that
1074 * the new row is appended to the bottom of the list of rows, and does not
1075 * require renumbering (of the rowIndex property) of previous rows.
1076 *
1077 * If you think you want a new blank row somewhere in the middle of the
1078 * terminal, look into moveRows_().
1079 *
1080 * This method does not pay attention to vtScrollTop/Bottom, since you should
1081 * be using moveRows() in cases where they would matter.
1082 *
1083 * The cursor will be positioned at column 0 of the first inserted line.
1084 */
1085hterm.Terminal.prototype.appendRows_ = function(count) {
1086 var cursorRow = this.screen_.rowsArray.length;
1087 var offset = this.scrollbackRows_.length + cursorRow;
1088 for (var i = 0; i < count; i++) {
1089 var row = this.document_.createElement('x-row');
1090 row.appendChild(this.document_.createTextNode(''));
1091 row.rowIndex = offset + i;
1092 this.screen_.pushRow(row);
1093 }
1094
1095 var extraRows = this.screen_.rowsArray.length - this.screenSize.height;
1096 if (extraRows > 0) {
1097 var ary = this.screen_.shiftRows(extraRows);
1098 Array.prototype.push.apply(this.scrollbackRows_, ary);
1099 this.scheduleScrollDown_();
1100 }
1101
1102 if (cursorRow >= this.screen_.rowsArray.length)
1103 cursorRow = this.screen_.rowsArray.length - 1;
1104
rginda87b86462011-12-14 13:48:03 -08001105 this.setAbsoluteCursorPosition(cursorRow, 0);
rginda8ba33642011-12-14 12:31:31 -08001106};
1107
1108/**
1109 * Relocate rows from one part of the addressable screen to another.
1110 *
1111 * This is used to recycle rows during VT scrolls (those which are driven
1112 * by VT commands, rather than by the user manipulating the scrollbar.)
1113 *
1114 * In this case, the blank lines scrolled into the scroll region are made of
1115 * the nodes we scrolled off. These have their rowIndex properties carefully
1116 * renumbered so as not to confuse the ScrollPort.
rginda8ba33642011-12-14 12:31:31 -08001117 */
1118hterm.Terminal.prototype.moveRows_ = function(fromIndex, count, toIndex) {
1119 var ary = this.screen_.removeRows(fromIndex, count);
1120 this.screen_.insertRows(toIndex, ary);
1121
1122 var start, end;
1123 if (fromIndex < toIndex) {
1124 start = fromIndex;
rginda87b86462011-12-14 13:48:03 -08001125 end = toIndex + count;
rginda8ba33642011-12-14 12:31:31 -08001126 } else {
1127 start = toIndex;
rginda87b86462011-12-14 13:48:03 -08001128 end = fromIndex + count;
rginda8ba33642011-12-14 12:31:31 -08001129 }
1130
1131 this.renumberRows_(start, end);
rginda2312fff2012-01-05 16:20:52 -08001132 this.scrollPort_.scheduleInvalidate();
rginda8ba33642011-12-14 12:31:31 -08001133};
1134
1135/**
1136 * Renumber the rowIndex property of the given range of rows.
1137 *
1138 * The start and end indicies are relative to the screen, not the scrollback.
1139 * Rows in the scrollback buffer cannot be renumbered. Since they are not
rginda2312fff2012-01-05 16:20:52 -08001140 * addressable (you can't delete them, scroll them, etc), you should have
rginda8ba33642011-12-14 12:31:31 -08001141 * no need to renumber scrollback rows.
1142 */
1143hterm.Terminal.prototype.renumberRows_ = function(start, end) {
1144 var offset = this.scrollbackRows_.length;
1145 for (var i = start; i < end; i++) {
1146 this.screen_.rowsArray[i].rowIndex = offset + i;
1147 }
1148};
1149
1150/**
1151 * Print a string to the terminal.
1152 *
1153 * This respects the current insert and wraparound modes. It will add new lines
1154 * to the end of the terminal, scrolling off the top into the scrollback buffer
1155 * if necessary.
1156 *
1157 * The string is *not* parsed for escape codes. Use the interpret() method if
1158 * that's what you're after.
1159 *
1160 * @param{string} str The string to print.
1161 */
1162hterm.Terminal.prototype.print = function(str) {
rgindaa19afe22012-01-25 15:40:22 -08001163 if (this.options_.wraparound && this.screen_.cursorPosition.overflow)
1164 this.newLine();
rginda2312fff2012-01-05 16:20:52 -08001165
rgindaa19afe22012-01-25 15:40:22 -08001166 if (this.options_.insertMode) {
1167 this.screen_.insertString(str);
1168 } else {
1169 this.screen_.overwriteString(str);
1170 }
1171
1172 var overflow = this.screen_.maybeClipCurrentRow();
1173
1174 if (this.options_.wraparound && overflow) {
1175 var lastColumn;
1176
1177 do {
rginda35c456b2012-02-09 17:29:05 -08001178 this.newLine();
1179 lastColumn = overflow.characterLength;
rgindaa19afe22012-01-25 15:40:22 -08001180
1181 if (!this.options_.insertMode)
1182 this.screen_.deleteChars(overflow.characterLength);
1183
1184 this.screen_.prependNodes(overflow);
rgindaa19afe22012-01-25 15:40:22 -08001185
1186 overflow = this.screen_.maybeClipCurrentRow();
1187 } while (overflow);
1188
1189 this.setCursorColumn(lastColumn);
1190 }
rginda8ba33642011-12-14 12:31:31 -08001191
1192 this.scheduleSyncCursorPosition_();
rginda0f5c0292012-01-13 11:00:13 -08001193
rginda9f5222b2012-03-05 11:53:28 -08001194 if (this.scrollOnOutput_)
rginda0f5c0292012-01-13 11:00:13 -08001195 this.scrollPort_.scrollRowToBottom(this.getRowCount());
rginda8ba33642011-12-14 12:31:31 -08001196};
1197
1198/**
rginda87b86462011-12-14 13:48:03 -08001199 * Set the VT scroll region.
1200 *
rginda87b86462011-12-14 13:48:03 -08001201 * This also resets the cursor position to the absolute (0, 0) position, since
1202 * that's what xterm appears to do.
1203 *
1204 * @param {integer} scrollTop The zero-based top of the scroll region.
1205 * @param {integer} scrollBottom The zero-based bottom of the scroll region,
1206 * inclusive.
1207 */
1208hterm.Terminal.prototype.setVTScrollRegion = function(scrollTop, scrollBottom) {
1209 this.vtScrollTop_ = scrollTop;
1210 this.vtScrollBottom_ = scrollBottom;
rginda87b86462011-12-14 13:48:03 -08001211};
1212
1213/**
rginda8ba33642011-12-14 12:31:31 -08001214 * Return the top row index according to the VT.
1215 *
1216 * This will return 0 unless the terminal has been told to restrict scrolling
1217 * to some lower row. It is used for some VT cursor positioning and scrolling
1218 * commands.
1219 *
1220 * @return {integer} The topmost row in the terminal's scroll region.
1221 */
1222hterm.Terminal.prototype.getVTScrollTop = function() {
1223 if (this.vtScrollTop_ != null)
1224 return this.vtScrollTop_;
1225
1226 return 0;
rginda87b86462011-12-14 13:48:03 -08001227};
rginda8ba33642011-12-14 12:31:31 -08001228
1229/**
1230 * Return the bottom row index according to the VT.
1231 *
1232 * This will return the height of the terminal unless the it has been told to
1233 * restrict scrolling to some higher row. It is used for some VT cursor
1234 * positioning and scrolling commands.
1235 *
1236 * @return {integer} The bottommost row in the terminal's scroll region.
1237 */
1238hterm.Terminal.prototype.getVTScrollBottom = function() {
1239 if (this.vtScrollBottom_ != null)
1240 return this.vtScrollBottom_;
1241
rginda87b86462011-12-14 13:48:03 -08001242 return this.screenSize.height - 1;
rginda8ba33642011-12-14 12:31:31 -08001243}
1244
1245/**
1246 * Process a '\n' character.
1247 *
1248 * If the cursor is on the final row of the terminal this will append a new
1249 * blank row to the screen and scroll the topmost row into the scrollback
1250 * buffer.
1251 *
1252 * Otherwise, this moves the cursor to column zero of the next row.
1253 */
1254hterm.Terminal.prototype.newLine = function() {
1255 if (this.screen_.cursorPosition.row == this.screen_.rowsArray.length - 1) {
rginda87b86462011-12-14 13:48:03 -08001256 // If we're at the end of the screen we need to append a new line and
1257 // scroll the top line into the scrollback buffer.
rginda8ba33642011-12-14 12:31:31 -08001258 this.appendRows_(1);
rginda87b86462011-12-14 13:48:03 -08001259 } else if (this.screen_.cursorPosition.row == this.getVTScrollBottom()) {
1260 // End of the scroll region does not affect the scrollback buffer.
1261 this.vtScrollUp(1);
1262 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, 0);
rginda8ba33642011-12-14 12:31:31 -08001263 } else {
rginda87b86462011-12-14 13:48:03 -08001264 // Anywhere else in the screen just moves the cursor.
1265 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row + 1, 0);
rginda8ba33642011-12-14 12:31:31 -08001266 }
1267};
1268
1269/**
1270 * Like newLine(), except maintain the cursor column.
1271 */
1272hterm.Terminal.prototype.lineFeed = function() {
1273 var column = this.screen_.cursorPosition.column;
1274 this.newLine();
1275 this.setCursorColumn(column);
1276};
1277
1278/**
rginda87b86462011-12-14 13:48:03 -08001279 * If autoCarriageReturn is set then newLine(), else lineFeed().
1280 */
1281hterm.Terminal.prototype.formFeed = function() {
1282 if (this.options_.autoCarriageReturn) {
1283 this.newLine();
1284 } else {
1285 this.lineFeed();
1286 }
1287};
1288
1289/**
1290 * Move the cursor up one row, possibly inserting a blank line.
1291 *
1292 * The cursor column is not changed.
1293 */
1294hterm.Terminal.prototype.reverseLineFeed = function() {
1295 var scrollTop = this.getVTScrollTop();
1296 var currentRow = this.screen_.cursorPosition.row;
1297
1298 if (currentRow == scrollTop) {
1299 this.insertLines(1);
1300 } else {
1301 this.setAbsoluteCursorRow(currentRow - 1);
1302 }
1303};
1304
1305/**
rginda8ba33642011-12-14 12:31:31 -08001306 * Replace all characters to the left of the current cursor with the space
1307 * character.
1308 *
1309 * TODO(rginda): This should probably *remove* the characters (not just replace
1310 * with a space) if there are no characters at or beyond the current cursor
1311 * position. Once it does that, it'll have the same text-attribute related
1312 * issues as hterm.Screen.prototype.clearCursorRow :/
1313 */
1314hterm.Terminal.prototype.eraseToLeft = function() {
rginda87b86462011-12-14 13:48:03 -08001315 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001316 this.setCursorColumn(0);
rgindacbbd7482012-06-13 15:06:16 -07001317 this.screen_.overwriteString(lib.f.getWhitespace(cursor.column + 1));
rginda87b86462011-12-14 13:48:03 -08001318 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001319};
1320
1321/**
David Benjamin684a9b72012-05-01 17:19:58 -04001322 * Erase a given number of characters to the right of the cursor.
rginda8ba33642011-12-14 12:31:31 -08001323 *
1324 * The cursor position is unchanged.
1325 *
1326 * TODO(rginda): Test that this works even when the cursor is positioned beyond
1327 * the end of the text.
1328 *
1329 * TODO(rginda): This likely has text-attribute related troubles similar to the
1330 * todo on hterm.Screen.prototype.clearCursorRow.
David Benjamin684a9b72012-05-01 17:19:58 -04001331 *
1332 * TODO(davidben): Probably better to not add the whitespace to the clipboard
1333 * if erasing to the end of the drawn portion of the line. That said, xterm
1334 * behaves the same here.
rginda8ba33642011-12-14 12:31:31 -08001335 */
1336hterm.Terminal.prototype.eraseToRight = function(opt_count) {
rginda87b86462011-12-14 13:48:03 -08001337 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001338
rginda87b86462011-12-14 13:48:03 -08001339 var maxCount = this.screenSize.width - cursor.column;
David Benjamin684a9b72012-05-01 17:19:58 -04001340 if (opt_count === undefined || opt_count >= maxCount) {
1341 this.screen_.deleteChars(maxCount);
1342 } else {
rgindacbbd7482012-06-13 15:06:16 -07001343 this.screen_.overwriteString(lib.f.getWhitespace(opt_count));
David Benjamin684a9b72012-05-01 17:19:58 -04001344 }
rginda87b86462011-12-14 13:48:03 -08001345 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001346 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001347};
1348
1349/**
1350 * Erase the current line.
1351 *
1352 * The cursor position is unchanged.
1353 *
1354 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1355 * has a text-attribute related TODO.
1356 */
1357hterm.Terminal.prototype.eraseLine = function() {
rginda87b86462011-12-14 13:48:03 -08001358 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001359 this.screen_.clearCursorRow();
rginda87b86462011-12-14 13:48:03 -08001360 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001361 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001362};
1363
1364/**
David Benjamina08d78f2012-05-05 00:28:49 -04001365 * Erase all characters from the start of the screen to the current cursor
1366 * position, regardless of scroll region.
rginda8ba33642011-12-14 12:31:31 -08001367 *
1368 * The cursor position is unchanged.
1369 *
1370 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1371 * has a text-attribute related TODO.
1372 */
1373hterm.Terminal.prototype.eraseAbove = function() {
rginda87b86462011-12-14 13:48:03 -08001374 var cursor = this.saveCursor();
1375
1376 this.eraseToLeft();
rginda8ba33642011-12-14 12:31:31 -08001377
David Benjamina08d78f2012-05-05 00:28:49 -04001378 for (var i = 0; i < cursor.row; i++) {
rginda87b86462011-12-14 13:48:03 -08001379 this.setAbsoluteCursorPosition(i, 0);
rginda8ba33642011-12-14 12:31:31 -08001380 this.screen_.clearCursorRow();
1381 }
1382
rginda87b86462011-12-14 13:48:03 -08001383 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001384 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001385};
1386
1387/**
1388 * Erase all characters from the current cursor position to the end of the
David Benjamina08d78f2012-05-05 00:28:49 -04001389 * screen, regardless of scroll region.
rginda8ba33642011-12-14 12:31:31 -08001390 *
1391 * The cursor position is unchanged.
1392 *
1393 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1394 * has a text-attribute related TODO.
1395 */
1396hterm.Terminal.prototype.eraseBelow = function() {
rginda87b86462011-12-14 13:48:03 -08001397 var cursor = this.saveCursor();
1398
1399 this.eraseToRight();
rginda8ba33642011-12-14 12:31:31 -08001400
David Benjamina08d78f2012-05-05 00:28:49 -04001401 var bottom = this.screenSize.height - 1;
rginda87b86462011-12-14 13:48:03 -08001402 for (var i = cursor.row + 1; i <= bottom; i++) {
1403 this.setAbsoluteCursorPosition(i, 0);
rginda8ba33642011-12-14 12:31:31 -08001404 this.screen_.clearCursorRow();
1405 }
1406
rginda87b86462011-12-14 13:48:03 -08001407 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001408 this.clearCursorOverflow();
rginda87b86462011-12-14 13:48:03 -08001409};
1410
1411/**
1412 * Fill the terminal with a given character.
1413 *
1414 * This methods does not respect the VT scroll region.
1415 *
1416 * @param {string} ch The character to use for the fill.
1417 */
1418hterm.Terminal.prototype.fill = function(ch) {
1419 var cursor = this.saveCursor();
1420
1421 this.setAbsoluteCursorPosition(0, 0);
1422 for (var row = 0; row < this.screenSize.height; row++) {
1423 for (var col = 0; col < this.screenSize.width; col++) {
1424 this.setAbsoluteCursorPosition(row, col);
1425 this.screen_.overwriteString(ch);
1426 }
1427 }
1428
1429 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001430};
1431
1432/**
rginda9ea433c2012-03-16 11:57:00 -07001433 * Erase the entire display and leave the cursor at (0, 0).
rginda8ba33642011-12-14 12:31:31 -08001434 *
rginda9ea433c2012-03-16 11:57:00 -07001435 * This does not respect the scroll region.
1436 *
1437 * @param {hterm.Screen} opt_screen Optional screen to operate on. Defaults
1438 * to the current screen.
rginda8ba33642011-12-14 12:31:31 -08001439 *
1440 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1441 * has a text-attribute related TODO.
1442 */
rginda9ea433c2012-03-16 11:57:00 -07001443hterm.Terminal.prototype.clearHome = function(opt_screen) {
1444 var screen = opt_screen || this.screen_;
1445 var bottom = screen.getHeight();
rginda8ba33642011-12-14 12:31:31 -08001446
rginda11057d52012-04-25 12:29:56 -07001447 if (bottom == 0) {
1448 // Empty screen, nothing to do.
1449 return;
1450 }
1451
rgindae4d29232012-01-19 10:47:13 -08001452 for (var i = 0; i < bottom; i++) {
rginda9ea433c2012-03-16 11:57:00 -07001453 screen.setCursorPosition(i, 0);
1454 screen.clearCursorRow();
rginda8ba33642011-12-14 12:31:31 -08001455 }
1456
rginda9ea433c2012-03-16 11:57:00 -07001457 screen.setCursorPosition(0, 0);
1458};
1459
1460/**
1461 * Erase the entire display without changing the cursor position.
1462 *
1463 * The cursor position is unchanged. This does not respect the scroll
1464 * region.
1465 *
1466 * @param {hterm.Screen} opt_screen Optional screen to operate on. Defaults
1467 * to the current screen.
1468 *
1469 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1470 * has a text-attribute related TODO.
1471 */
1472hterm.Terminal.prototype.clear = function(opt_screen) {
1473 var screen = opt_screen || this.screen_;
1474 var cursor = screen.cursorPosition.clone();
1475 this.clearHome(screen);
1476 screen.setCursorPosition(cursor.row, cursor.column);
rginda8ba33642011-12-14 12:31:31 -08001477};
1478
1479/**
1480 * VT command to insert lines at the current cursor row.
1481 *
1482 * This respects the current scroll region. Rows pushed off the bottom are
1483 * lost (they won't show up in the scrollback buffer).
1484 *
1485 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1486 * has a text-attribute related TODO.
1487 *
1488 * @param {integer} count The number of lines to insert.
1489 */
1490hterm.Terminal.prototype.insertLines = function(count) {
rginda87b86462011-12-14 13:48:03 -08001491 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001492
1493 var bottom = this.getVTScrollBottom();
rginda87b86462011-12-14 13:48:03 -08001494 count = Math.min(count, bottom - cursor.row);
rginda8ba33642011-12-14 12:31:31 -08001495
rgindae4d29232012-01-19 10:47:13 -08001496 var start = bottom - count + 1;
rginda87b86462011-12-14 13:48:03 -08001497 if (start != cursor.row)
1498 this.moveRows_(start, count, cursor.row);
rginda8ba33642011-12-14 12:31:31 -08001499
1500 for (var i = 0; i < count; i++) {
rginda87b86462011-12-14 13:48:03 -08001501 this.setAbsoluteCursorPosition(cursor.row + i, 0);
rginda8ba33642011-12-14 12:31:31 -08001502 this.screen_.clearCursorRow();
1503 }
1504
rginda87b86462011-12-14 13:48:03 -08001505 cursor.column = 0;
1506 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001507};
1508
1509/**
1510 * VT command to delete lines at the current cursor row.
1511 *
1512 * New rows are added to the bottom of scroll region to take their place. New
1513 * rows are strictly there to take up space and have no content or style.
1514 */
1515hterm.Terminal.prototype.deleteLines = function(count) {
rginda87b86462011-12-14 13:48:03 -08001516 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001517
rginda87b86462011-12-14 13:48:03 -08001518 var top = cursor.row;
rginda8ba33642011-12-14 12:31:31 -08001519 var bottom = this.getVTScrollBottom();
1520
rginda87b86462011-12-14 13:48:03 -08001521 var maxCount = bottom - top + 1;
rginda8ba33642011-12-14 12:31:31 -08001522 count = Math.min(count, maxCount);
1523
rginda87b86462011-12-14 13:48:03 -08001524 var moveStart = bottom - count + 1;
rginda8ba33642011-12-14 12:31:31 -08001525 if (count != maxCount)
1526 this.moveRows_(top, count, moveStart);
1527
1528 for (var i = 0; i < count; i++) {
rginda87b86462011-12-14 13:48:03 -08001529 this.setAbsoluteCursorPosition(moveStart + i, 0);
rginda8ba33642011-12-14 12:31:31 -08001530 this.screen_.clearCursorRow();
1531 }
1532
rginda87b86462011-12-14 13:48:03 -08001533 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001534 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001535};
1536
1537/**
1538 * Inserts the given number of spaces at the current cursor position.
1539 *
rginda87b86462011-12-14 13:48:03 -08001540 * The cursor position is not changed.
rginda8ba33642011-12-14 12:31:31 -08001541 */
1542hterm.Terminal.prototype.insertSpace = function(count) {
rginda87b86462011-12-14 13:48:03 -08001543 var cursor = this.saveCursor();
1544
rgindacbbd7482012-06-13 15:06:16 -07001545 var ws = lib.f.getWhitespace(count || 1);
rginda8ba33642011-12-14 12:31:31 -08001546 this.screen_.insertString(ws);
rgindaa19afe22012-01-25 15:40:22 -08001547 this.screen_.maybeClipCurrentRow();
rginda87b86462011-12-14 13:48:03 -08001548
1549 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001550 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001551};
1552
1553/**
1554 * Forward-delete the specified number of characters starting at the cursor
1555 * position.
1556 *
1557 * @param {integer} count The number of characters to delete.
1558 */
1559hterm.Terminal.prototype.deleteChars = function(count) {
1560 this.screen_.deleteChars(count);
David Benjamin54e8bf62012-06-01 22:31:40 -04001561 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001562};
1563
1564/**
1565 * Shift rows in the scroll region upwards by a given number of lines.
1566 *
1567 * New rows are inserted at the bottom of the scroll region to fill the
1568 * vacated rows. The new rows not filled out with the current text attributes.
1569 *
1570 * This function does not affect the scrollback rows at all. Rows shifted
1571 * off the top are lost.
1572 *
rginda87b86462011-12-14 13:48:03 -08001573 * The cursor position is not altered.
1574 *
rginda8ba33642011-12-14 12:31:31 -08001575 * @param {integer} count The number of rows to scroll.
1576 */
1577hterm.Terminal.prototype.vtScrollUp = function(count) {
rginda87b86462011-12-14 13:48:03 -08001578 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001579
rginda87b86462011-12-14 13:48:03 -08001580 this.setAbsoluteCursorRow(this.getVTScrollTop());
rginda8ba33642011-12-14 12:31:31 -08001581 this.deleteLines(count);
1582
rginda87b86462011-12-14 13:48:03 -08001583 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001584};
1585
1586/**
1587 * Shift rows below the cursor down by a given number of lines.
1588 *
1589 * This function respects the current scroll region.
1590 *
1591 * New rows are inserted at the top of the scroll region to fill the
1592 * vacated rows. The new rows not filled out with the current text attributes.
1593 *
1594 * This function does not affect the scrollback rows at all. Rows shifted
1595 * off the bottom are lost.
1596 *
1597 * @param {integer} count The number of rows to scroll.
1598 */
1599hterm.Terminal.prototype.vtScrollDown = function(opt_count) {
rginda87b86462011-12-14 13:48:03 -08001600 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001601
rginda87b86462011-12-14 13:48:03 -08001602 this.setAbsoluteCursorPosition(this.getVTScrollTop(), 0);
rginda8ba33642011-12-14 12:31:31 -08001603 this.insertLines(opt_count);
1604
rginda87b86462011-12-14 13:48:03 -08001605 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001606};
1607
rginda87b86462011-12-14 13:48:03 -08001608
rginda8ba33642011-12-14 12:31:31 -08001609/**
1610 * Set the cursor position.
1611 *
1612 * The cursor row is relative to the scroll region if the terminal has
1613 * 'origin mode' enabled, or relative to the addressable screen otherwise.
1614 *
1615 * @param {integer} row The new zero-based cursor row.
1616 * @param {integer} row The new zero-based cursor column.
1617 */
1618hterm.Terminal.prototype.setCursorPosition = function(row, column) {
1619 if (this.options_.originMode) {
rginda87b86462011-12-14 13:48:03 -08001620 this.setRelativeCursorPosition(row, column);
rginda8ba33642011-12-14 12:31:31 -08001621 } else {
rginda87b86462011-12-14 13:48:03 -08001622 this.setAbsoluteCursorPosition(row, column);
rginda8ba33642011-12-14 12:31:31 -08001623 }
rginda87b86462011-12-14 13:48:03 -08001624};
rginda8ba33642011-12-14 12:31:31 -08001625
rginda87b86462011-12-14 13:48:03 -08001626hterm.Terminal.prototype.setRelativeCursorPosition = function(row, column) {
1627 var scrollTop = this.getVTScrollTop();
rgindacbbd7482012-06-13 15:06:16 -07001628 row = lib.f.clamp(row + scrollTop, scrollTop, this.getVTScrollBottom());
1629 column = lib.f.clamp(column, 0, this.screenSize.width - 1);
rginda87b86462011-12-14 13:48:03 -08001630 this.screen_.setCursorPosition(row, column);
1631};
1632
1633hterm.Terminal.prototype.setAbsoluteCursorPosition = function(row, column) {
rgindacbbd7482012-06-13 15:06:16 -07001634 row = lib.f.clamp(row, 0, this.screenSize.height - 1);
1635 column = lib.f.clamp(column, 0, this.screenSize.width - 1);
rginda8ba33642011-12-14 12:31:31 -08001636 this.screen_.setCursorPosition(row, column);
1637};
1638
1639/**
1640 * Set the cursor column.
1641 *
1642 * @param {integer} column The new zero-based cursor column.
1643 */
1644hterm.Terminal.prototype.setCursorColumn = function(column) {
rginda87b86462011-12-14 13:48:03 -08001645 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, column);
rginda8ba33642011-12-14 12:31:31 -08001646};
1647
1648/**
1649 * Return the cursor column.
1650 *
1651 * @return {integer} The zero-based cursor column.
1652 */
1653hterm.Terminal.prototype.getCursorColumn = function() {
1654 return this.screen_.cursorPosition.column;
1655};
1656
1657/**
1658 * Set the cursor row.
1659 *
1660 * The cursor row is relative to the scroll region if the terminal has
1661 * 'origin mode' enabled, or relative to the addressable screen otherwise.
1662 *
1663 * @param {integer} row The new cursor row.
1664 */
rginda87b86462011-12-14 13:48:03 -08001665hterm.Terminal.prototype.setAbsoluteCursorRow = function(row) {
1666 this.setAbsoluteCursorPosition(row, this.screen_.cursorPosition.column);
rginda8ba33642011-12-14 12:31:31 -08001667};
1668
1669/**
1670 * Return the cursor row.
1671 *
1672 * @return {integer} The zero-based cursor row.
1673 */
1674hterm.Terminal.prototype.getCursorRow = function(row) {
1675 return this.screen_.cursorPosition.row;
1676};
1677
1678/**
1679 * Request that the ScrollPort redraw itself soon.
1680 *
1681 * The redraw will happen asynchronously, soon after the call stack winds down.
1682 * Multiple calls will be coalesced into a single redraw.
1683 */
1684hterm.Terminal.prototype.scheduleRedraw_ = function() {
rginda87b86462011-12-14 13:48:03 -08001685 if (this.timeouts_.redraw)
1686 return;
rginda8ba33642011-12-14 12:31:31 -08001687
1688 var self = this;
rginda87b86462011-12-14 13:48:03 -08001689 this.timeouts_.redraw = setTimeout(function() {
1690 delete self.timeouts_.redraw;
rginda8ba33642011-12-14 12:31:31 -08001691 self.scrollPort_.redraw_();
1692 }, 0);
1693};
1694
1695/**
1696 * Request that the ScrollPort be scrolled to the bottom.
1697 *
1698 * The scroll will happen asynchronously, soon after the call stack winds down.
1699 * Multiple calls will be coalesced into a single scroll.
1700 *
1701 * This affects the scrollbar position of the ScrollPort, and has nothing to
1702 * do with the VT scroll commands.
1703 */
1704hterm.Terminal.prototype.scheduleScrollDown_ = function() {
1705 if (this.timeouts_.scrollDown)
rginda87b86462011-12-14 13:48:03 -08001706 return;
rginda8ba33642011-12-14 12:31:31 -08001707
1708 var self = this;
1709 this.timeouts_.scrollDown = setTimeout(function() {
1710 delete self.timeouts_.scrollDown;
1711 self.scrollPort_.scrollRowToBottom(self.getRowCount());
1712 }, 10);
1713};
1714
1715/**
1716 * Move the cursor up a specified number of rows.
1717 *
1718 * @param {integer} count The number of rows to move the cursor.
1719 */
1720hterm.Terminal.prototype.cursorUp = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001721 return this.cursorDown(-(count || 1));
rginda8ba33642011-12-14 12:31:31 -08001722};
1723
1724/**
1725 * Move the cursor down a specified number of rows.
1726 *
1727 * @param {integer} count The number of rows to move the cursor.
1728 */
1729hterm.Terminal.prototype.cursorDown = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001730 count = count || 1;
rginda8ba33642011-12-14 12:31:31 -08001731 var minHeight = (this.options_.originMode ? this.getVTScrollTop() : 0);
1732 var maxHeight = (this.options_.originMode ? this.getVTScrollBottom() :
1733 this.screenSize.height - 1);
1734
rgindacbbd7482012-06-13 15:06:16 -07001735 var row = lib.f.clamp(this.screen_.cursorPosition.row + count,
rginda8ba33642011-12-14 12:31:31 -08001736 minHeight, maxHeight);
rginda87b86462011-12-14 13:48:03 -08001737 this.setAbsoluteCursorRow(row);
rginda8ba33642011-12-14 12:31:31 -08001738};
1739
1740/**
1741 * Move the cursor left a specified number of columns.
1742 *
1743 * @param {integer} count The number of columns to move the cursor.
1744 */
1745hterm.Terminal.prototype.cursorLeft = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001746 return this.cursorRight(-(count || 1));
rginda8ba33642011-12-14 12:31:31 -08001747};
1748
1749/**
1750 * Move the cursor right a specified number of columns.
1751 *
1752 * @param {integer} count The number of columns to move the cursor.
1753 */
1754hterm.Terminal.prototype.cursorRight = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001755 count = count || 1;
rgindacbbd7482012-06-13 15:06:16 -07001756 var column = lib.f.clamp(this.screen_.cursorPosition.column + count,
rginda87b86462011-12-14 13:48:03 -08001757 0, this.screenSize.width - 1);
rginda8ba33642011-12-14 12:31:31 -08001758 this.setCursorColumn(column);
1759};
1760
1761/**
1762 * Reverse the foreground and background colors of the terminal.
1763 *
1764 * This only affects text that was drawn with no attributes.
1765 *
1766 * TODO(rginda): Test xterm to see if reverse is respected for text that has
1767 * been drawn with attributes that happen to coincide with the default
1768 * 'no-attribute' colors. My guess is probably not.
1769 */
1770hterm.Terminal.prototype.setReverseVideo = function(state) {
rginda87b86462011-12-14 13:48:03 -08001771 this.options_.reverseVideo = state;
rginda8ba33642011-12-14 12:31:31 -08001772 if (state) {
rginda9f5222b2012-03-05 11:53:28 -08001773 this.scrollPort_.setForegroundColor(this.prefs_.get('background-color'));
1774 this.scrollPort_.setBackgroundColor(this.prefs_.get('foreground-color'));
rginda8ba33642011-12-14 12:31:31 -08001775 } else {
rginda9f5222b2012-03-05 11:53:28 -08001776 this.scrollPort_.setForegroundColor(this.prefs_.get('foreground-color'));
1777 this.scrollPort_.setBackgroundColor(this.prefs_.get('background-color'));
rginda8ba33642011-12-14 12:31:31 -08001778 }
1779};
1780
1781/**
rginda87b86462011-12-14 13:48:03 -08001782 * Ring the terminal bell.
rginda87b86462011-12-14 13:48:03 -08001783 */
1784hterm.Terminal.prototype.ringBell = function() {
rginda9f5222b2012-03-05 11:53:28 -08001785 if (this.bellAudio_.getAttribute('src'))
1786 this.bellAudio_.play();
rgindaf0090c92012-02-10 14:58:52 -08001787
rginda6d397402012-01-17 10:58:29 -08001788 this.cursorNode_.style.backgroundColor =
1789 this.scrollPort_.getForegroundColor();
rginda87b86462011-12-14 13:48:03 -08001790
1791 var self = this;
1792 setTimeout(function() {
rginda9f5222b2012-03-05 11:53:28 -08001793 self.cursorNode_.style.backgroundColor = self.prefs_.get('cursor-color');
rginda6d397402012-01-17 10:58:29 -08001794 }, 200);
rginda87b86462011-12-14 13:48:03 -08001795};
1796
1797/**
rginda8ba33642011-12-14 12:31:31 -08001798 * Set the origin mode bit.
1799 *
1800 * If origin mode is on, certain VT cursor and scrolling commands measure their
1801 * row parameter relative to the VT scroll region. Otherwise, row 0 corresponds
1802 * to the top of the addressable screen.
1803 *
1804 * Defaults to off.
1805 *
1806 * @param {boolean} state True to set origin mode, false to unset.
1807 */
1808hterm.Terminal.prototype.setOriginMode = function(state) {
1809 this.options_.originMode = state;
rgindae4d29232012-01-19 10:47:13 -08001810 this.setCursorPosition(0, 0);
rginda8ba33642011-12-14 12:31:31 -08001811};
1812
1813/**
1814 * Set the insert mode bit.
1815 *
1816 * If insert mode is on, existing text beyond the cursor position will be
1817 * shifted right to make room for new text. Otherwise, new text overwrites
1818 * any existing text.
1819 *
1820 * Defaults to off.
1821 *
1822 * @param {boolean} state True to set insert mode, false to unset.
1823 */
1824hterm.Terminal.prototype.setInsertMode = function(state) {
1825 this.options_.insertMode = state;
1826};
1827
1828/**
rginda87b86462011-12-14 13:48:03 -08001829 * Set the auto carriage return bit.
1830 *
1831 * If auto carriage return is on then a formfeed character is interpreted
1832 * as a newline, otherwise it's the same as a linefeed. The difference boils
1833 * down to whether or not the cursor column is reset.
1834 */
1835hterm.Terminal.prototype.setAutoCarriageReturn = function(state) {
1836 this.options_.autoCarriageReturn = state;
1837};
1838
1839/**
rginda8ba33642011-12-14 12:31:31 -08001840 * Set the wraparound mode bit.
1841 *
1842 * If wraparound mode is on, certain VT commands will allow the cursor to wrap
1843 * to the start of the following row. Otherwise, the cursor is clamped to the
1844 * end of the screen and attempts to write past it are ignored.
1845 *
1846 * Defaults to on.
1847 *
1848 * @param {boolean} state True to set wraparound mode, false to unset.
1849 */
1850hterm.Terminal.prototype.setWraparound = function(state) {
1851 this.options_.wraparound = state;
1852};
1853
1854/**
1855 * Set the reverse-wraparound mode bit.
1856 *
1857 * If wraparound mode is off, certain VT commands will allow the cursor to wrap
1858 * to the end of the previous row. Otherwise, the cursor is clamped to column
1859 * 0.
1860 *
1861 * Defaults to off.
1862 *
1863 * @param {boolean} state True to set reverse-wraparound mode, false to unset.
1864 */
1865hterm.Terminal.prototype.setReverseWraparound = function(state) {
1866 this.options_.reverseWraparound = state;
1867};
1868
1869/**
1870 * Selects between the primary and alternate screens.
1871 *
1872 * If alternate mode is on, the alternate screen is active. Otherwise the
1873 * primary screen is active.
1874 *
1875 * Swapping screens has no effect on the scrollback buffer.
1876 *
1877 * Each screen maintains its own cursor position.
1878 *
1879 * Defaults to off.
1880 *
1881 * @param {boolean} state True to set alternate mode, false to unset.
1882 */
1883hterm.Terminal.prototype.setAlternateMode = function(state) {
rginda6d397402012-01-17 10:58:29 -08001884 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001885 this.screen_ = state ? this.alternateScreen_ : this.primaryScreen_;
1886
rginda35c456b2012-02-09 17:29:05 -08001887 if (this.screen_.rowsArray.length &&
1888 this.screen_.rowsArray[0].rowIndex != this.scrollbackRows_.length) {
1889 // If the screen changed sizes while we were away, our rowIndexes may
1890 // be incorrect.
1891 var offset = this.scrollbackRows_.length;
1892 var ary = this.screen_.rowsArray;
rgindacbbd7482012-06-13 15:06:16 -07001893 for (var i = 0; i < ary.length; i++) {
rginda35c456b2012-02-09 17:29:05 -08001894 ary[i].rowIndex = offset + i;
1895 }
1896 }
rginda8ba33642011-12-14 12:31:31 -08001897
rginda35c456b2012-02-09 17:29:05 -08001898 this.realizeWidth_(this.screenSize.width);
1899 this.realizeHeight_(this.screenSize.height);
1900 this.scrollPort_.syncScrollHeight();
1901 this.scrollPort_.invalidate();
rginda8ba33642011-12-14 12:31:31 -08001902
rginda6d397402012-01-17 10:58:29 -08001903 this.restoreCursor(cursor);
rginda35c456b2012-02-09 17:29:05 -08001904 this.scrollPort_.resize();
rginda8ba33642011-12-14 12:31:31 -08001905};
1906
1907/**
1908 * Set the cursor-blink mode bit.
1909 *
1910 * If cursor-blink is on, the cursor will blink when it is visible. Otherwise
1911 * a visible cursor does not blink.
1912 *
1913 * You should make sure to turn blinking off if you're going to dispose of a
1914 * terminal, otherwise you'll leak a timeout.
1915 *
1916 * Defaults to on.
1917 *
1918 * @param {boolean} state True to set cursor-blink mode, false to unset.
1919 */
1920hterm.Terminal.prototype.setCursorBlink = function(state) {
1921 this.options_.cursorBlink = state;
1922
1923 if (!state && this.timeouts_.cursorBlink) {
1924 clearTimeout(this.timeouts_.cursorBlink);
1925 delete this.timeouts_.cursorBlink;
1926 }
1927
1928 if (this.options_.cursorVisible)
1929 this.setCursorVisible(true);
1930};
1931
1932/**
1933 * Set the cursor-visible mode bit.
1934 *
1935 * If cursor-visible is on, the cursor will be visible. Otherwise it will not.
1936 *
1937 * Defaults to on.
1938 *
1939 * @param {boolean} state True to set cursor-visible mode, false to unset.
1940 */
1941hterm.Terminal.prototype.setCursorVisible = function(state) {
1942 this.options_.cursorVisible = state;
1943
1944 if (!state) {
rginda87b86462011-12-14 13:48:03 -08001945 this.cursorNode_.style.opacity = '0';
rginda8ba33642011-12-14 12:31:31 -08001946 return;
1947 }
1948
rginda87b86462011-12-14 13:48:03 -08001949 this.syncCursorPosition_();
1950
1951 this.cursorNode_.style.opacity = '1';
rginda8ba33642011-12-14 12:31:31 -08001952
1953 if (this.options_.cursorBlink) {
1954 if (this.timeouts_.cursorBlink)
1955 return;
1956
1957 this.timeouts_.cursorBlink = setInterval(this.onCursorBlink_.bind(this),
1958 500);
1959 } else {
1960 if (this.timeouts_.cursorBlink) {
1961 clearTimeout(this.timeouts_.cursorBlink);
1962 delete this.timeouts_.cursorBlink;
1963 }
1964 }
1965};
1966
1967/**
rginda87b86462011-12-14 13:48:03 -08001968 * Synchronizes the visible cursor and document selection with the current
1969 * cursor coordinates.
rginda8ba33642011-12-14 12:31:31 -08001970 */
1971hterm.Terminal.prototype.syncCursorPosition_ = function() {
1972 var topRowIndex = this.scrollPort_.getTopRowIndex();
1973 var bottomRowIndex = this.scrollPort_.getBottomRowIndex(topRowIndex);
1974 var cursorRowIndex = this.scrollbackRows_.length +
1975 this.screen_.cursorPosition.row;
1976
1977 if (cursorRowIndex > bottomRowIndex) {
1978 // Cursor is scrolled off screen, move it outside of the visible area.
rginda35c456b2012-02-09 17:29:05 -08001979 this.cursorNode_.style.top = -this.scrollPort_.characterSize.height + 'px';
rginda8ba33642011-12-14 12:31:31 -08001980 return;
1981 }
1982
rginda35c456b2012-02-09 17:29:05 -08001983 this.cursorNode_.style.width = this.scrollPort_.characterSize.width + 'px';
1984 this.cursorNode_.style.height = this.scrollPort_.characterSize.height + 'px';
1985
rginda8ba33642011-12-14 12:31:31 -08001986 this.cursorNode_.style.top = this.scrollPort_.visibleRowTopMargin +
rginda35c456b2012-02-09 17:29:05 -08001987 this.scrollPort_.characterSize.height * (cursorRowIndex - topRowIndex) +
1988 'px';
1989 this.cursorNode_.style.left = this.scrollPort_.characterSize.width *
1990 this.screen_.cursorPosition.column + 'px';
rginda87b86462011-12-14 13:48:03 -08001991
1992 this.cursorNode_.setAttribute('title',
1993 '(' + this.screen_.cursorPosition.row +
1994 ', ' + this.screen_.cursorPosition.column +
1995 ')');
1996
1997 // Update the caret for a11y purposes.
1998 var selection = this.document_.getSelection();
1999 if (selection && selection.isCollapsed)
2000 this.screen_.syncSelectionCaret(selection);
rginda8ba33642011-12-14 12:31:31 -08002001};
2002
2003/**
2004 * Synchronizes the visible cursor with the current cursor coordinates.
2005 *
2006 * The sync will happen asynchronously, soon after the call stack winds down.
2007 * Multiple calls will be coalesced into a single sync.
2008 */
2009hterm.Terminal.prototype.scheduleSyncCursorPosition_ = function() {
2010 if (this.timeouts_.syncCursor)
rginda87b86462011-12-14 13:48:03 -08002011 return;
rginda8ba33642011-12-14 12:31:31 -08002012
2013 var self = this;
2014 this.timeouts_.syncCursor = setTimeout(function() {
2015 self.syncCursorPosition_();
2016 delete self.timeouts_.syncCursor;
rginda87b86462011-12-14 13:48:03 -08002017 }, 0);
2018};
2019
rgindacc2996c2012-02-24 14:59:31 -08002020/**
rgindaf522ce02012-04-17 17:49:17 -07002021 * Show or hide the zoom warning.
2022 *
2023 * The zoom warning is a message warning the user that their browser zoom must
2024 * be set to 100% in order for hterm to function properly.
2025 *
2026 * @param {boolean} state True to show the message, false to hide it.
2027 */
2028hterm.Terminal.prototype.showZoomWarning_ = function(state) {
2029 if (!this.zoomWarningNode_) {
2030 if (!state)
2031 return;
2032
2033 this.zoomWarningNode_ = this.document_.createElement('div');
2034 this.zoomWarningNode_.style.cssText = (
2035 'color: black;' +
2036 'background-color: #ff2222;' +
2037 'font-size: large;' +
2038 'border-radius: 8px;' +
2039 'opacity: 0.75;' +
2040 'padding: 0.2em 0.5em 0.2em 0.5em;' +
2041 'top: 0.5em;' +
2042 'right: 1.2em;' +
2043 'position: absolute;' +
2044 '-webkit-text-size-adjust: none;' +
2045 '-webkit-user-select: none;');
rgindaf522ce02012-04-17 17:49:17 -07002046 }
2047
rgindade84e382012-04-20 15:39:31 -07002048 this.zoomWarningNode_.textContent = hterm.msg('ZOOM_WARNING') ||
2049 ('!! ' + parseInt(this.scrollPort_.characterSize.zoomFactor * 100) +
2050 '% !!');
rgindaf522ce02012-04-17 17:49:17 -07002051 this.zoomWarningNode_.style.fontFamily = this.prefs_.get('font-family');
2052
2053 if (state) {
2054 if (!this.zoomWarningNode_.parentNode)
2055 this.div_.parentNode.appendChild(this.zoomWarningNode_);
2056 } else if (this.zoomWarningNode_.parentNode) {
2057 this.zoomWarningNode_.parentNode.removeChild(this.zoomWarningNode_);
2058 }
2059};
2060
2061/**
rgindacc2996c2012-02-24 14:59:31 -08002062 * Show the terminal overlay for a given amount of time.
2063 *
2064 * The terminal overlay appears in inverse video in a large font, centered
2065 * over the terminal. You should probably keep the overlay message brief,
2066 * since it's in a large font and you probably aren't going to check the size
2067 * of the terminal first.
2068 *
2069 * @param {string} msg The text (not HTML) message to display in the overlay.
2070 * @param {number} opt_timeout The amount of time to wait before fading out
2071 * the overlay. Defaults to 1.5 seconds. Pass null to have the overlay
2072 * stay up forever (or until the next overlay).
2073 */
2074hterm.Terminal.prototype.showOverlay = function(msg, opt_timeout) {
rgindaf0090c92012-02-10 14:58:52 -08002075 if (!this.overlayNode_) {
2076 if (!this.div_)
2077 return;
2078
2079 this.overlayNode_ = this.document_.createElement('div');
2080 this.overlayNode_.style.cssText = (
rgindaf0090c92012-02-10 14:58:52 -08002081 'border-radius: 15px;' +
rgindaf0090c92012-02-10 14:58:52 -08002082 'font-size: xx-large;' +
2083 'opacity: 0.75;' +
2084 'padding: 0.2em 0.5em 0.2em 0.5em;' +
2085 'position: absolute;' +
2086 '-webkit-user-select: none;' +
2087 '-webkit-transition: opacity 180ms ease-in;');
2088 }
2089
rginda9f5222b2012-03-05 11:53:28 -08002090 this.overlayNode_.style.color = this.prefs_.get('background-color');
2091 this.overlayNode_.style.backgroundColor = this.prefs_.get('foreground-color');
2092 this.overlayNode_.style.fontFamily = this.prefs_.get('font-family');
2093
rgindaf0090c92012-02-10 14:58:52 -08002094 this.overlayNode_.textContent = msg;
2095 this.overlayNode_.style.opacity = '0.75';
2096
2097 if (!this.overlayNode_.parentNode)
2098 this.div_.appendChild(this.overlayNode_);
2099
2100 this.overlayNode_.style.top = (
2101 this.div_.clientHeight - this.overlayNode_.clientHeight) / 2;
2102 this.overlayNode_.style.left = (
2103 this.div_.clientWidth - this.overlayNode_.clientWidth -
2104 this.scrollbarWidthPx) / 2;
2105
2106 var self = this;
2107
2108 if (this.overlayTimeout_)
2109 clearTimeout(this.overlayTimeout_);
2110
rgindacc2996c2012-02-24 14:59:31 -08002111 if (opt_timeout === null)
2112 return;
2113
rgindaf0090c92012-02-10 14:58:52 -08002114 this.overlayTimeout_ = setTimeout(function() {
2115 self.overlayNode_.style.opacity = '0';
2116 setTimeout(function() {
rginda259dcca2012-03-14 16:37:11 -07002117 if (self.overlayNode_.parentNode)
2118 self.overlayNode_.parentNode.removeChild(self.overlayNode_);
rgindaf0090c92012-02-10 14:58:52 -08002119 self.overlayTimeout_ = null;
2120 self.overlayNode_.style.opacity = '0.75';
2121 }, 200);
rgindacc2996c2012-02-24 14:59:31 -08002122 }, opt_timeout || 1500);
rgindaf0090c92012-02-10 14:58:52 -08002123};
2124
2125hterm.Terminal.prototype.overlaySize = function() {
2126 this.showOverlay(this.screenSize.width + 'x' + this.screenSize.height);
2127};
2128
rginda87b86462011-12-14 13:48:03 -08002129/**
2130 * Invoked by hterm.Terminal.Keyboard when a VT keystroke is detected.
2131 *
2132 * @param {string} string The VT string representing the keystroke.
2133 */
2134hterm.Terminal.prototype.onVTKeystroke = function(string) {
rginda9f5222b2012-03-05 11:53:28 -08002135 if (this.scrollOnKeystroke_)
rginda87b86462011-12-14 13:48:03 -08002136 this.scrollPort_.scrollRowToBottom(this.getRowCount());
2137
2138 this.io.onVTKeystroke(string);
rginda8ba33642011-12-14 12:31:31 -08002139};
2140
2141/**
rginda8e92a692012-05-20 19:37:20 -07002142 * React when focus changes.
2143 */
2144hterm.Terminal.prototype.onFocusChange_ = function(state) {
2145 this.cursorNode_.setAttribute('focus', state ? 'true' : 'false');
2146};
2147
2148/**
rginda8ba33642011-12-14 12:31:31 -08002149 * React when the ScrollPort is scrolled.
2150 */
2151hterm.Terminal.prototype.onScroll_ = function() {
2152 this.scheduleSyncCursorPosition_();
2153};
2154
2155/**
rginda9846e2f2012-01-27 13:53:33 -08002156 * React when text is pasted into the scrollPort.
2157 */
2158hterm.Terminal.prototype.onPaste_ = function(e) {
2159 this.io.onVTKeystroke(e.text);
2160};
2161
2162/**
rginda8ba33642011-12-14 12:31:31 -08002163 * React when the ScrollPort is resized.
rgindac9bc5502012-01-18 11:48:44 -08002164 *
2165 * Note: This function should not directly contain code that alters the internal
2166 * state of the terminal. That kind of code belongs in realizeWidth or
2167 * realizeHeight, so that it can be executed synchronously in the case of a
2168 * programmatic width change.
rginda8ba33642011-12-14 12:31:31 -08002169 */
2170hterm.Terminal.prototype.onResize_ = function() {
rgindac9bc5502012-01-18 11:48:44 -08002171 var columnCount = Math.floor(this.scrollPort_.getScreenWidth() /
rginda35c456b2012-02-09 17:29:05 -08002172 this.scrollPort_.characterSize.width);
2173 var rowCount = Math.floor(this.scrollPort_.getScreenHeight() /
2174 this.scrollPort_.characterSize.height);
2175
2176 if (!(columnCount || rowCount)) {
2177 // We avoid these situations since they happen sometimes when the terminal
2178 // gets removed from the document, and we can't deal with that.
2179 return;
2180 }
2181
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +04002182 this.realizeSize_(columnCount, rowCount);
rgindac9bc5502012-01-18 11:48:44 -08002183 this.scheduleSyncCursorPosition_();
rgindaf522ce02012-04-17 17:49:17 -07002184 this.showZoomWarning_(this.scrollPort_.characterSize.zoomFactor != 1);
rgindaf0090c92012-02-10 14:58:52 -08002185 this.overlaySize();
rginda8ba33642011-12-14 12:31:31 -08002186};
2187
2188/**
2189 * Service the cursor blink timeout.
2190 */
2191hterm.Terminal.prototype.onCursorBlink_ = function() {
rginda87b86462011-12-14 13:48:03 -08002192 if (this.cursorNode_.style.opacity == '0') {
2193 this.cursorNode_.style.opacity = '1';
rginda8ba33642011-12-14 12:31:31 -08002194 } else {
rginda87b86462011-12-14 13:48:03 -08002195 this.cursorNode_.style.opacity = '0';
rginda8ba33642011-12-14 12:31:31 -08002196 }
2197};
David Reveman8f552492012-03-28 12:18:41 -04002198
2199/**
2200 * Set the scrollbar-visible mode bit.
2201 *
2202 * If scrollbar-visible is on, the vertical scrollbar will be visible.
2203 * Otherwise it will not.
2204 *
2205 * Defaults to on.
2206 *
2207 * @param {boolean} state True to set scrollbar-visible mode, false to unset.
2208 */
2209hterm.Terminal.prototype.setScrollbarVisible = function(state) {
2210 this.scrollPort_.setScrollbarVisible(state);
2211};