blob: 2ac6edac319d152f57db123242332f56781ac681 [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
Robert Gindab4839c22013-02-28 16:52:10 -08007lib.rtdep('lib.colors', 'lib.PreferenceManager', 'lib.resource',
Robert Ginda57f03b42012-09-13 11:02:48 -07008 'hterm.Keyboard', 'hterm.Options', 'hterm.PreferenceManager',
Ricky Liang48f05cb2013-12-31 23:35:29 +08009 'hterm.Screen', 'hterm.ScrollPort', 'hterm.Size',
10 'hterm.TextAttributes', 'hterm.VT');
rgindacbbd7482012-06-13 15:06:16 -070011
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 *
Robert Ginda57f03b42012-09-13 11:02:48 -070029 * @param {string} opt_profileId Optional preference profile name. If not
rginda9f5222b2012-03-05 11:53:28 -080030 * provided, defaults to 'default'.
rginda8ba33642011-12-14 12:31:31 -080031 */
Robert Ginda57f03b42012-09-13 11:02:48 -070032hterm.Terminal = function(opt_profileId) {
33 this.profileId_ = null;
rginda9f5222b2012-03-05 11:53:28 -080034
rginda8ba33642011-12-14 12:31:31 -080035 // Two screen instances.
36 this.primaryScreen_ = new hterm.Screen();
37 this.alternateScreen_ = new hterm.Screen();
38
39 // The "current" screen.
40 this.screen_ = this.primaryScreen_;
41
rginda8ba33642011-12-14 12:31:31 -080042 // The local notion of the screen size. ScreenBuffers also have a size which
43 // indicates their present size. During size changes, the two may disagree.
44 // Also, the inactive screen's size is not altered until it is made the active
45 // screen.
46 this.screenSize = new hterm.Size(0, 0);
47
rginda8ba33642011-12-14 12:31:31 -080048 // The scroll port we'll be using to display the visible rows.
rginda35c456b2012-02-09 17:29:05 -080049 this.scrollPort_ = new hterm.ScrollPort(this);
rginda8ba33642011-12-14 12:31:31 -080050 this.scrollPort_.subscribe('resize', this.onResize_.bind(this));
51 this.scrollPort_.subscribe('scroll', this.onScroll_.bind(this));
rginda9846e2f2012-01-27 13:53:33 -080052 this.scrollPort_.subscribe('paste', this.onPaste_.bind(this));
rgindaa09e7332012-08-17 12:49:51 -070053 this.scrollPort_.onCopy = this.onCopy_.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
Robert Ginda830583c2013-08-07 13:20:46 -070081 // The current cursor shape of the terminal.
82 this.cursorShape_ = hterm.Terminal.cursorShape.BLOCK;
83
84 // The current color of the cursor.
85 this.cursorColor_ = null;
86
rginda9f5222b2012-03-05 11:53:28 -080087 // These prefs are cached so we don't have to read from local storage with
Robert Ginda57f03b42012-09-13 11:02:48 -070088 // each output and keystroke. They are initialized by the preference manager.
Robert Ginda8cb7d902013-06-20 14:37:18 -070089 this.backgroundColor_ = null;
90 this.foregroundColor_ = null;
Robert Ginda57f03b42012-09-13 11:02:48 -070091 this.scrollOnOutput_ = null;
92 this.scrollOnKeystroke_ = null;
rginda9f5222b2012-03-05 11:53:28 -080093
rgindaf0090c92012-02-10 14:58:52 -080094 // Terminal bell sound.
95 this.bellAudio_ = this.document_.createElement('audio');
rgindaf0090c92012-02-10 14:58:52 -080096 this.bellAudio_.setAttribute('preload', 'auto');
97
rginda6d397402012-01-17 10:58:29 -080098 // Cursor position and attributes saved with DECSC.
99 this.savedOptions_ = {};
100
rginda8ba33642011-12-14 12:31:31 -0800101 // The current mode bits for the terminal.
102 this.options_ = new hterm.Options();
103
104 // Timeouts we might need to clear.
105 this.timeouts_ = {};
rginda87b86462011-12-14 13:48:03 -0800106
107 // The VT escape sequence interpreter.
rginda0f5c0292012-01-13 11:00:13 -0800108 this.vt = new hterm.VT(this);
rginda87b86462011-12-14 13:48:03 -0800109
rgindafeaf3142012-01-31 15:14:20 -0800110 // The keyboard hander.
111 this.keyboard = new hterm.Keyboard(this);
112
rginda87b86462011-12-14 13:48:03 -0800113 // General IO interface that can be given to third parties without exposing
114 // the entire terminal object.
115 this.io = new hterm.Terminal.IO(this);
rgindac9bc5502012-01-18 11:48:44 -0800116
rgindad5613292012-06-19 15:40:37 -0700117 // True if mouse-click-drag should scroll the terminal.
118 this.enableMouseDragScroll = true;
119
Robert Ginda57f03b42012-09-13 11:02:48 -0700120 this.copyOnSelect = null;
rginda4bba5e12012-06-20 16:15:30 -0700121 this.mousePasteButton = null;
rginda4bba5e12012-06-20 16:15:30 -0700122
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +0400123 this.realizeSize_(80, 24);
rgindac9bc5502012-01-18 11:48:44 -0800124 this.setDefaultTabStops();
Robert Ginda57f03b42012-09-13 11:02:48 -0700125
126 this.setProfile(opt_profileId || 'default',
127 function() { this.onTerminalReady() }.bind(this));
rginda87b86462011-12-14 13:48:03 -0800128};
129
130/**
Robert Ginda830583c2013-08-07 13:20:46 -0700131 * Possible cursor shapes.
132 */
133hterm.Terminal.cursorShape = {
134 BLOCK: 'BLOCK',
135 BEAM: 'BEAM',
136 UNDERLINE: 'UNDERLINE'
137};
138
139/**
Robert Ginda57f03b42012-09-13 11:02:48 -0700140 * Clients should override this to be notified when the terminal is ready
141 * for use.
142 *
143 * The terminal initialization is asynchronous, and shouldn't be used before
144 * this method is called.
145 */
146hterm.Terminal.prototype.onTerminalReady = function() { };
147
148/**
rginda35c456b2012-02-09 17:29:05 -0800149 * Default tab with of 8 to match xterm.
150 */
151hterm.Terminal.prototype.tabWidth = 8;
152
153/**
rginda9f5222b2012-03-05 11:53:28 -0800154 * Select a preference profile.
155 *
156 * This will load the terminal preferences for the given profile name and
157 * associate subsequent preference changes with the new preference profile.
158 *
159 * @param {string} newName The name of the preference profile. Forward slash
160 * characters will be removed from the name.
Robert Ginda57f03b42012-09-13 11:02:48 -0700161 * @param {function} opt_callback Optional callback to invoke when the profile
162 * transition is complete.
rginda9f5222b2012-03-05 11:53:28 -0800163 */
Robert Ginda57f03b42012-09-13 11:02:48 -0700164hterm.Terminal.prototype.setProfile = function(profileId, opt_callback) {
165 this.profileId_ = profileId.replace(/\//g, '');
rginda9f5222b2012-03-05 11:53:28 -0800166
Robert Ginda57f03b42012-09-13 11:02:48 -0700167 var terminal = this;
rginda9f5222b2012-03-05 11:53:28 -0800168
Robert Ginda57f03b42012-09-13 11:02:48 -0700169 if (this.prefs_)
170 this.prefs_.deactivate();
rginda9f5222b2012-03-05 11:53:28 -0800171
Robert Ginda57f03b42012-09-13 11:02:48 -0700172 this.prefs_ = new hterm.PreferenceManager(this.profileId_);
173 this.prefs_.addObservers(null, {
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700174 'alt-backspace-is-meta-backspace': function(v) {
175 terminal.keyboard.altBackspaceIsMetaBackspace = v;
176 },
177
Robert Ginda57f03b42012-09-13 11:02:48 -0700178 'alt-is-meta': function(v) {
179 terminal.keyboard.altIsMeta = v;
180 },
181
182 'alt-sends-what': function(v) {
183 if (!/^(escape|8-bit|browser-key)$/.test(v))
184 v = 'escape';
185
186 terminal.keyboard.altSendsWhat = v;
187 },
188
189 'audible-bell-sound': function(v) {
Robert Gindab4839c22013-02-28 16:52:10 -0800190 var ary = v.match(/^lib-resource:(\S+)/);
191 if (ary) {
192 terminal.bellAudio_.setAttribute('src',
193 lib.resource.getDataUrl(ary[1]));
194 } else {
195 terminal.bellAudio_.setAttribute('src', v);
196 }
Robert Ginda57f03b42012-09-13 11:02:48 -0700197 },
198
199 'background-color': function(v) {
200 terminal.setBackgroundColor(v);
201 },
202
203 'background-image': function(v) {
204 terminal.scrollPort_.setBackgroundImage(v);
205 },
206
207 'background-size': function(v) {
208 terminal.scrollPort_.setBackgroundSize(v);
209 },
210
211 'background-position': function(v) {
212 terminal.scrollPort_.setBackgroundPosition(v);
213 },
214
215 'backspace-sends-backspace': function(v) {
216 terminal.keyboard.backspaceSendsBackspace = v;
217 },
218
219 'cursor-blink': function(v) {
220 terminal.setCursorBlink(!!v);
221 },
222
223 'cursor-color': function(v) {
224 terminal.setCursorColor(v);
225 },
226
227 'color-palette-overrides': function(v) {
228 if (!(v == null || v instanceof Object || v instanceof Array)) {
229 console.warn('Preference color-palette-overrides is not an array or ' +
230 'object: ' + v);
231 return;
rginda9f5222b2012-03-05 11:53:28 -0800232 }
rginda9f5222b2012-03-05 11:53:28 -0800233
Robert Ginda57f03b42012-09-13 11:02:48 -0700234 lib.colors.colorPalette = lib.colors.stockColorPalette.concat();
rginda39bdf6f2012-04-10 16:50:55 -0700235
Robert Ginda57f03b42012-09-13 11:02:48 -0700236 if (v) {
237 for (var key in v) {
238 var i = parseInt(key);
239 if (isNaN(i) || i < 0 || i > 255) {
240 console.log('Invalid value in palette: ' + key + ': ' + v[key]);
241 continue;
242 }
243
244 if (v[i]) {
245 var rgb = lib.colors.normalizeCSS(v[i]);
246 if (rgb)
247 lib.colors.colorPalette[i] = rgb;
248 }
249 }
rginda30f20f62012-04-05 16:36:19 -0700250 }
rginda30f20f62012-04-05 16:36:19 -0700251
Robert Ginda57f03b42012-09-13 11:02:48 -0700252 terminal.primaryScreen_.textAttributes.resetColorPalette()
253 terminal.alternateScreen_.textAttributes.resetColorPalette();
254 },
rginda30f20f62012-04-05 16:36:19 -0700255
Robert Ginda57f03b42012-09-13 11:02:48 -0700256 'copy-on-select': function(v) {
257 terminal.copyOnSelect = !!v;
258 },
rginda9f5222b2012-03-05 11:53:28 -0800259
Leonardo Mesquita61e7c312014-01-04 12:53:12 +0100260 'ctrl-v-paste': function(v) {
261 terminal.keyboard.ctrlVPaste = v;
262 },
263
Robert Ginda57f03b42012-09-13 11:02:48 -0700264 'enable-8-bit-control': function(v) {
265 terminal.vt.enable8BitControl = !!v;
266 },
rginda30f20f62012-04-05 16:36:19 -0700267
Robert Ginda57f03b42012-09-13 11:02:48 -0700268 'enable-bold': function(v) {
269 terminal.syncBoldSafeState();
270 },
Philip Douglass959b49d2012-05-30 13:29:29 -0400271
Robert Ginda57f03b42012-09-13 11:02:48 -0700272 'enable-clipboard-write': function(v) {
273 terminal.vt.enableClipboardWrite = !!v;
274 },
Philip Douglass959b49d2012-05-30 13:29:29 -0400275
Robert Ginda3755e752013-05-31 13:34:09 -0700276 'enable-dec12': function(v) {
277 terminal.vt.enableDec12 = !!v;
278 },
279
Robert Ginda57f03b42012-09-13 11:02:48 -0700280 'font-family': function(v) {
281 terminal.syncFontFamily();
282 },
rginda30f20f62012-04-05 16:36:19 -0700283
Robert Ginda57f03b42012-09-13 11:02:48 -0700284 'font-size': function(v) {
285 terminal.setFontSize(v);
286 },
rginda9875d902012-08-20 16:21:57 -0700287
Robert Ginda57f03b42012-09-13 11:02:48 -0700288 'font-smoothing': function(v) {
289 terminal.syncFontFamily();
290 },
rgindade84e382012-04-20 15:39:31 -0700291
Robert Ginda57f03b42012-09-13 11:02:48 -0700292 'foreground-color': function(v) {
293 terminal.setForegroundColor(v);
294 },
rginda30f20f62012-04-05 16:36:19 -0700295
Robert Ginda57f03b42012-09-13 11:02:48 -0700296 'home-keys-scroll': function(v) {
297 terminal.keyboard.homeKeysScroll = v;
298 },
rginda4bba5e12012-06-20 16:15:30 -0700299
Robert Ginda57f03b42012-09-13 11:02:48 -0700300 'max-string-sequence': function(v) {
301 terminal.vt.maxStringSequence = v;
302 },
rginda11057d52012-04-25 12:29:56 -0700303
Andrew de los Reyes6af23ae2013-04-04 14:17:50 -0700304 'media-keys-are-fkeys': function(v) {
305 terminal.keyboard.mediaKeysAreFKeys = v;
306 },
307
Robert Ginda57f03b42012-09-13 11:02:48 -0700308 'meta-sends-escape': function(v) {
309 terminal.keyboard.metaSendsEscape = v;
310 },
rginda30f20f62012-04-05 16:36:19 -0700311
Robert Ginda57f03b42012-09-13 11:02:48 -0700312 'mouse-cell-motion-trick': function(v) {
313 terminal.vt.setMouseCellMotionTrick(v);
314 },
Robert Ginda9fb38222012-09-11 14:19:12 -0700315
Robert Ginda57f03b42012-09-13 11:02:48 -0700316 'mouse-paste-button': function(v) {
317 terminal.syncMousePasteButton();
318 },
rgindaa8ba17d2012-08-15 14:41:10 -0700319
Robert Ginda40932892012-12-10 17:26:40 -0800320 'pass-alt-number': function(v) {
321 if (v == null) {
322 var osx = window.navigator.userAgent.match(/Mac OS X/);
323
324 // Let Alt-1..9 pass to the browser (to control tab switching) on
325 // non-OS X systems, or if hterm is not opened in an app window.
326 v = (!osx && hterm.windowType != 'popup');
327 }
328
329 terminal.passAltNumber = v;
330 },
331
332 'pass-ctrl-number': function(v) {
333 if (v == null) {
334 var osx = window.navigator.userAgent.match(/Mac OS X/);
335
336 // Let Ctrl-1..9 pass to the browser (to control tab switching) on
337 // non-OS X systems, or if hterm is not opened in an app window.
338 v = (!osx && hterm.windowType != 'popup');
339 }
340
341 terminal.passCtrlNumber = v;
342 },
343
344 'pass-meta-number': function(v) {
345 if (v == null) {
346 var osx = window.navigator.userAgent.match(/Mac OS X/);
347
348 // Let Meta-1..9 pass to the browser (to control tab switching) on
349 // OS X systems, or if hterm is not opened in an app window.
350 v = (osx && hterm.windowType != 'popup');
351 }
352
353 terminal.passMetaNumber = v;
354 },
355
Robert Ginda8cb7d902013-06-20 14:37:18 -0700356 'receive-encoding': function(v) {
357 if (!(/^(utf-8|raw)$/).test(v)) {
358 console.warn('Invalid value for "receive-encoding": ' + v);
359 v = 'utf-8';
360 }
361
362 terminal.vt.characterEncoding = v;
363 },
364
Robert Ginda57f03b42012-09-13 11:02:48 -0700365 'scroll-on-keystroke': function(v) {
366 terminal.scrollOnKeystroke_ = v;
367 },
rginda9f5222b2012-03-05 11:53:28 -0800368
Robert Ginda57f03b42012-09-13 11:02:48 -0700369 'scroll-on-output': function(v) {
370 terminal.scrollOnOutput_ = v;
371 },
rginda30f20f62012-04-05 16:36:19 -0700372
Robert Ginda57f03b42012-09-13 11:02:48 -0700373 'scrollbar-visible': function(v) {
374 terminal.setScrollbarVisible(v);
375 },
rginda9f5222b2012-03-05 11:53:28 -0800376
Robert Ginda8cb7d902013-06-20 14:37:18 -0700377 'send-encoding': function(v) {
378 if (!(/^(utf-8|raw)$/).test(v)) {
379 console.warn('Invalid value for "send-encoding": ' + v);
380 v = 'utf-8';
381 }
382
383 terminal.keyboard.characterEncoding = v;
384 },
385
Robert Ginda57f03b42012-09-13 11:02:48 -0700386 'shift-insert-paste': function(v) {
387 terminal.keyboard.shiftInsertPaste = v;
388 },
rginda9f5222b2012-03-05 11:53:28 -0800389
Ricky Liang48f05cb2013-12-31 23:35:29 +0800390 'page-keys-scroll': function(v) {
391 terminal.keyboard.pageKeysScroll = v;
Robert Ginda57f03b42012-09-13 11:02:48 -0700392 }
393 });
rginda30f20f62012-04-05 16:36:19 -0700394
Robert Ginda57f03b42012-09-13 11:02:48 -0700395 this.prefs_.readStorage(function() {
rginda9f5222b2012-03-05 11:53:28 -0800396 this.prefs_.notifyAll();
Robert Ginda57f03b42012-09-13 11:02:48 -0700397
398 if (opt_callback)
399 opt_callback();
400 }.bind(this));
rginda9f5222b2012-03-05 11:53:28 -0800401};
402
rginda8e92a692012-05-20 19:37:20 -0700403/**
404 * Set the color for the cursor.
405 *
406 * If you want this setting to persist, set it through prefs_, rather than
407 * with this method.
408 */
409hterm.Terminal.prototype.setCursorColor = function(color) {
Robert Ginda830583c2013-08-07 13:20:46 -0700410 this.cursorColor_ = color;
rginda8e92a692012-05-20 19:37:20 -0700411 this.cursorNode_.style.backgroundColor = color;
412 this.cursorNode_.style.borderColor = color;
413};
414
415/**
416 * Return the current cursor color as a string.
417 */
418hterm.Terminal.prototype.getCursorColor = function() {
Robert Ginda830583c2013-08-07 13:20:46 -0700419 return this.cursorColor_;
rginda8e92a692012-05-20 19:37:20 -0700420};
421
422/**
rgindad5613292012-06-19 15:40:37 -0700423 * Enable or disable mouse based text selection in the terminal.
424 */
425hterm.Terminal.prototype.setSelectionEnabled = function(state) {
426 this.enableMouseDragScroll = state;
427 this.scrollPort_.setSelectionEnabled(state);
428};
429
430/**
rginda8e92a692012-05-20 19:37:20 -0700431 * Set the background color.
432 *
433 * If you want this setting to persist, set it through prefs_, rather than
434 * with this method.
435 */
436hterm.Terminal.prototype.setBackgroundColor = function(color) {
rgindacbbd7482012-06-13 15:06:16 -0700437 this.backgroundColor_ = lib.colors.normalizeCSS(color);
Robert Ginda57f03b42012-09-13 11:02:48 -0700438 this.primaryScreen_.textAttributes.setDefaults(
439 this.foregroundColor_, this.backgroundColor_);
440 this.alternateScreen_.textAttributes.setDefaults(
441 this.foregroundColor_, this.backgroundColor_);
rginda8e92a692012-05-20 19:37:20 -0700442 this.scrollPort_.setBackgroundColor(color);
443};
444
rginda9f5222b2012-03-05 11:53:28 -0800445/**
446 * Return the current terminal background color.
447 *
448 * Intended for use by other classes, so we don't have to expose the entire
449 * prefs_ object.
450 */
451hterm.Terminal.prototype.getBackgroundColor = function() {
rginda8e92a692012-05-20 19:37:20 -0700452 return this.backgroundColor_;
453};
454
455/**
456 * Set the foreground color.
457 *
458 * If you want this setting to persist, set it through prefs_, rather than
459 * with this method.
460 */
461hterm.Terminal.prototype.setForegroundColor = function(color) {
rgindacbbd7482012-06-13 15:06:16 -0700462 this.foregroundColor_ = lib.colors.normalizeCSS(color);
Robert Ginda57f03b42012-09-13 11:02:48 -0700463 this.primaryScreen_.textAttributes.setDefaults(
464 this.foregroundColor_, this.backgroundColor_);
465 this.alternateScreen_.textAttributes.setDefaults(
466 this.foregroundColor_, this.backgroundColor_);
rginda8e92a692012-05-20 19:37:20 -0700467 this.scrollPort_.setForegroundColor(color);
rginda9f5222b2012-03-05 11:53:28 -0800468};
469
470/**
471 * Return the current terminal foreground color.
472 *
473 * Intended for use by other classes, so we don't have to expose the entire
474 * prefs_ object.
475 */
476hterm.Terminal.prototype.getForegroundColor = function() {
rginda8e92a692012-05-20 19:37:20 -0700477 return this.foregroundColor_;
rginda9f5222b2012-03-05 11:53:28 -0800478};
479
480/**
rginda87b86462011-12-14 13:48:03 -0800481 * Create a new instance of a terminal command and run it with a given
482 * argument string.
483 *
484 * @param {function} commandClass The constructor for a terminal command.
485 * @param {string} argString The argument string to pass to the command.
486 */
487hterm.Terminal.prototype.runCommandClass = function(commandClass, argString) {
rgindaf522ce02012-04-17 17:49:17 -0700488 var environment = this.prefs_.get('environment');
489 if (typeof environment != 'object' || environment == null)
490 environment = {};
491
rginda87b86462011-12-14 13:48:03 -0800492 var self = this;
493 this.command = new commandClass(
494 { argString: argString || '',
495 io: this.io.push(),
rgindaf522ce02012-04-17 17:49:17 -0700496 environment: environment,
rginda87b86462011-12-14 13:48:03 -0800497 onExit: function(code) {
498 self.io.pop();
rgindafeaf3142012-01-31 15:14:20 -0800499 self.uninstallKeyboard();
rginda9875d902012-08-20 16:21:57 -0700500 if (self.prefs_.get('close-on-exit'))
501 window.close();
rginda87b86462011-12-14 13:48:03 -0800502 }
503 });
504
rgindafeaf3142012-01-31 15:14:20 -0800505 this.installKeyboard();
rginda87b86462011-12-14 13:48:03 -0800506 this.command.run();
507};
508
509/**
rgindafeaf3142012-01-31 15:14:20 -0800510 * Returns true if the current screen is the primary screen, false otherwise.
511 */
512hterm.Terminal.prototype.isPrimaryScreen = function() {
rgindaf522ce02012-04-17 17:49:17 -0700513 return this.screen_ == this.primaryScreen_;
rgindafeaf3142012-01-31 15:14:20 -0800514};
515
516/**
517 * Install the keyboard handler for this terminal.
518 *
519 * This will prevent the browser from seeing any keystrokes sent to the
520 * terminal.
521 */
522hterm.Terminal.prototype.installKeyboard = function() {
Toni Barzic0bfa8922013-11-22 11:18:35 -0800523 this.keyboard.installKeyboard(this.scrollPort_.getScreenNode());
rgindafeaf3142012-01-31 15:14:20 -0800524}
525
526/**
527 * Uninstall the keyboard handler for this terminal.
528 */
529hterm.Terminal.prototype.uninstallKeyboard = function() {
530 this.keyboard.installKeyboard(null);
531}
532
533/**
rginda35c456b2012-02-09 17:29:05 -0800534 * Set the font size for this terminal.
rginda9f5222b2012-03-05 11:53:28 -0800535 *
536 * Call setFontSize(0) to reset to the default font size.
537 *
538 * This function does not modify the font-size preference.
539 *
540 * @param {number} px The desired font size, in pixels.
rginda35c456b2012-02-09 17:29:05 -0800541 */
542hterm.Terminal.prototype.setFontSize = function(px) {
rginda9f5222b2012-03-05 11:53:28 -0800543 if (px === 0)
544 px = this.prefs_.get('font-size');
545
rginda35c456b2012-02-09 17:29:05 -0800546 this.scrollPort_.setFontSize(px);
Ricky Liang48f05cb2013-12-31 23:35:29 +0800547 if (this.wcCssRule_) {
548 this.wcCssRule_.style.width = this.scrollPort_.characterSize.width * 2 +
549 'px';
550 }
rginda35c456b2012-02-09 17:29:05 -0800551};
552
553/**
554 * Get the current font size.
555 */
556hterm.Terminal.prototype.getFontSize = function() {
557 return this.scrollPort_.getFontSize();
558};
559
560/**
rginda8e92a692012-05-20 19:37:20 -0700561 * Get the current font family.
562 */
563hterm.Terminal.prototype.getFontFamily = function() {
564 return this.scrollPort_.getFontFamily();
565};
566
567/**
rginda35c456b2012-02-09 17:29:05 -0800568 * Set the CSS "font-family" for this terminal.
569 */
rginda9f5222b2012-03-05 11:53:28 -0800570hterm.Terminal.prototype.syncFontFamily = function() {
571 this.scrollPort_.setFontFamily(this.prefs_.get('font-family'),
572 this.prefs_.get('font-smoothing'));
573 this.syncBoldSafeState();
574};
575
rginda4bba5e12012-06-20 16:15:30 -0700576/**
577 * Set this.mousePasteButton based on the mouse-paste-button pref,
578 * autodetecting if necessary.
579 */
580hterm.Terminal.prototype.syncMousePasteButton = function() {
581 var button = this.prefs_.get('mouse-paste-button');
582 if (typeof button == 'number') {
583 this.mousePasteButton = button;
584 return;
585 }
586
587 var ary = navigator.userAgent.match(/\(X11;\s+(\S+)/);
588 if (!ary || ary[2] == 'CrOS') {
589 this.mousePasteButton = 2;
590 } else {
591 this.mousePasteButton = 3;
592 }
593};
594
595/**
596 * Enable or disable bold based on the enable-bold pref, autodetecting if
597 * necessary.
598 */
rginda9f5222b2012-03-05 11:53:28 -0800599hterm.Terminal.prototype.syncBoldSafeState = function() {
600 var enableBold = this.prefs_.get('enable-bold');
601 if (enableBold !== null) {
Robert Gindaed016262012-10-26 16:27:09 -0700602 this.primaryScreen_.textAttributes.enableBold = enableBold;
603 this.alternateScreen_.textAttributes.enableBold = enableBold;
rginda9f5222b2012-03-05 11:53:28 -0800604 return;
605 }
606
rgindaf7521392012-02-28 17:20:34 -0800607 var normalSize = this.scrollPort_.measureCharacterSize();
608 var boldSize = this.scrollPort_.measureCharacterSize('bold');
609
610 var isBoldSafe = normalSize.equals(boldSize);
rgindaf7521392012-02-28 17:20:34 -0800611 if (!isBoldSafe) {
612 console.warn('Bold characters disabled: Size of bold weight differs ' +
rgindac9759de2012-03-19 13:21:41 -0700613 'from normal. Font family is: ' +
614 this.scrollPort_.getFontFamily());
rgindaf7521392012-02-28 17:20:34 -0800615 }
rginda9f5222b2012-03-05 11:53:28 -0800616
Robert Gindaed016262012-10-26 16:27:09 -0700617 this.primaryScreen_.textAttributes.enableBold = isBoldSafe;
618 this.alternateScreen_.textAttributes.enableBold = isBoldSafe;
rginda35c456b2012-02-09 17:29:05 -0800619};
620
621/**
rginda87b86462011-12-14 13:48:03 -0800622 * Return a copy of the current cursor position.
623 *
624 * @return {hterm.RowCol} The RowCol object representing the current position.
625 */
626hterm.Terminal.prototype.saveCursor = function() {
627 return this.screen_.cursorPosition.clone();
628};
629
rgindaa19afe22012-01-25 15:40:22 -0800630hterm.Terminal.prototype.getTextAttributes = function() {
631 return this.screen_.textAttributes;
632};
633
rginda1a09aa02012-06-18 21:11:25 -0700634hterm.Terminal.prototype.setTextAttributes = function(textAttributes) {
635 this.screen_.textAttributes = textAttributes;
636};
637
rginda87b86462011-12-14 13:48:03 -0800638/**
rgindaf522ce02012-04-17 17:49:17 -0700639 * Return the current browser zoom factor applied to the terminal.
640 *
641 * @return {number} The current browser zoom factor.
642 */
643hterm.Terminal.prototype.getZoomFactor = function() {
644 return this.scrollPort_.characterSize.zoomFactor;
645};
646
647/**
rginda9846e2f2012-01-27 13:53:33 -0800648 * Change the title of this terminal's window.
649 */
650hterm.Terminal.prototype.setWindowTitle = function(title) {
rgindafeaf3142012-01-31 15:14:20 -0800651 window.document.title = title;
rginda9846e2f2012-01-27 13:53:33 -0800652};
653
654/**
rginda87b86462011-12-14 13:48:03 -0800655 * Restore a previously saved cursor position.
656 *
657 * @param {hterm.RowCol} cursor The position to restore.
658 */
659hterm.Terminal.prototype.restoreCursor = function(cursor) {
rgindacbbd7482012-06-13 15:06:16 -0700660 var row = lib.f.clamp(cursor.row, 0, this.screenSize.height - 1);
661 var column = lib.f.clamp(cursor.column, 0, this.screenSize.width - 1);
rginda35c456b2012-02-09 17:29:05 -0800662 this.screen_.setCursorPosition(row, column);
663 if (cursor.column > column ||
664 cursor.column == column && cursor.overflow) {
665 this.screen_.cursorPosition.overflow = true;
666 }
rginda87b86462011-12-14 13:48:03 -0800667};
668
669/**
David Benjamin54e8bf62012-06-01 22:31:40 -0400670 * Clear the cursor's overflow flag.
671 */
672hterm.Terminal.prototype.clearCursorOverflow = function() {
673 this.screen_.cursorPosition.overflow = false;
674};
675
676/**
Robert Ginda830583c2013-08-07 13:20:46 -0700677 * Sets the cursor shape
678 */
679hterm.Terminal.prototype.setCursorShape = function(shape) {
680 this.cursorShape_ = shape;
Robert Gindafb1be6a2013-12-11 11:56:22 -0800681 this.restyleCursor_();
Robert Ginda830583c2013-08-07 13:20:46 -0700682}
683
684/**
685 * Get the cursor shape
686 */
687hterm.Terminal.prototype.getCursorShape = function() {
688 return this.cursorShape_;
689}
690
691/**
rginda87b86462011-12-14 13:48:03 -0800692 * Set the width of the terminal, resizing the UI to match.
693 */
694hterm.Terminal.prototype.setWidth = function(columnCount) {
rgindaf0090c92012-02-10 14:58:52 -0800695 if (columnCount == null) {
696 this.div_.style.width = '100%';
697 return;
698 }
699
rginda35c456b2012-02-09 17:29:05 -0800700 this.div_.style.width = this.scrollPort_.characterSize.width *
Robert Ginda97769282013-02-01 15:30:30 -0800701 columnCount + this.scrollPort_.currentScrollbarWidthPx + 'px';
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +0400702 this.realizeSize_(columnCount, this.screenSize.height);
rgindac9bc5502012-01-18 11:48:44 -0800703 this.scheduleSyncCursorPosition_();
704};
rginda87b86462011-12-14 13:48:03 -0800705
rgindac9bc5502012-01-18 11:48:44 -0800706/**
rginda35c456b2012-02-09 17:29:05 -0800707 * Set the height of the terminal, resizing the UI to match.
708 */
709hterm.Terminal.prototype.setHeight = function(rowCount) {
rgindaf0090c92012-02-10 14:58:52 -0800710 if (rowCount == null) {
711 this.div_.style.height = '100%';
712 return;
713 }
714
rginda35c456b2012-02-09 17:29:05 -0800715 this.div_.style.height =
rginda30f20f62012-04-05 16:36:19 -0700716 this.scrollPort_.characterSize.height * rowCount + 'px';
rginda35c456b2012-02-09 17:29:05 -0800717 this.realizeSize_(this.screenSize.width, rowCount);
718 this.scheduleSyncCursorPosition_();
719};
720
721/**
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +0400722 * Deal with terminal size changes.
723 *
724 */
725hterm.Terminal.prototype.realizeSize_ = function(columnCount, rowCount) {
726 if (columnCount != this.screenSize.width)
727 this.realizeWidth_(columnCount);
728
729 if (rowCount != this.screenSize.height)
730 this.realizeHeight_(rowCount);
731
732 // Send new terminal size to plugin.
Robert Gindae81427f2013-05-24 10:34:46 -0700733 this.io.onTerminalResize_(columnCount, rowCount);
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +0400734};
735
736/**
rgindac9bc5502012-01-18 11:48:44 -0800737 * Deal with terminal width changes.
738 *
739 * This function does what needs to be done when the terminal width changes
740 * out from under us. It happens here rather than in onResize_() because this
741 * code may need to run synchronously to handle programmatic changes of
742 * terminal width.
743 *
744 * Relying on the browser to send us an async resize event means we may not be
745 * in the correct state yet when the next escape sequence hits.
746 */
747hterm.Terminal.prototype.realizeWidth_ = function(columnCount) {
Robert Ginda4e83f3a2012-09-04 15:25:25 -0700748 if (columnCount <= 0)
749 throw new Error('Attempt to realize bad width: ' + columnCount);
750
rgindac9bc5502012-01-18 11:48:44 -0800751 var deltaColumns = columnCount - this.screen_.getWidth();
752
rginda87b86462011-12-14 13:48:03 -0800753 this.screenSize.width = columnCount;
754 this.screen_.setColumnCount(columnCount);
rgindac9bc5502012-01-18 11:48:44 -0800755
756 if (deltaColumns > 0) {
David Benjamin66e954d2012-05-05 21:08:12 -0400757 if (this.defaultTabStops)
758 this.setDefaultTabStops(this.screenSize.width - deltaColumns);
rgindac9bc5502012-01-18 11:48:44 -0800759 } else {
760 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
David Benjamin66e954d2012-05-05 21:08:12 -0400761 if (this.tabStops_[i] < columnCount)
rgindac9bc5502012-01-18 11:48:44 -0800762 break;
763
764 this.tabStops_.pop();
765 }
766 }
767
768 this.screen_.setColumnCount(this.screenSize.width);
769};
770
771/**
772 * Deal with terminal height changes.
773 *
774 * This function does what needs to be done when the terminal height changes
775 * out from under us. It happens here rather than in onResize_() because this
776 * code may need to run synchronously to handle programmatic changes of
777 * terminal height.
778 *
779 * Relying on the browser to send us an async resize event means we may not be
780 * in the correct state yet when the next escape sequence hits.
781 */
782hterm.Terminal.prototype.realizeHeight_ = function(rowCount) {
Robert Ginda4e83f3a2012-09-04 15:25:25 -0700783 if (rowCount <= 0)
784 throw new Error('Attempt to realize bad height: ' + rowCount);
785
rgindac9bc5502012-01-18 11:48:44 -0800786 var deltaRows = rowCount - this.screen_.getHeight();
787
788 this.screenSize.height = rowCount;
789
790 var cursor = this.saveCursor();
791
792 if (deltaRows < 0) {
793 // Screen got smaller.
794 deltaRows *= -1;
795 while (deltaRows) {
796 var lastRow = this.getRowCount() - 1;
797 if (lastRow - this.scrollbackRows_.length == cursor.row)
798 break;
799
800 if (this.getRowText(lastRow))
801 break;
802
803 this.screen_.popRow();
804 deltaRows--;
805 }
806
807 var ary = this.screen_.shiftRows(deltaRows);
808 this.scrollbackRows_.push.apply(this.scrollbackRows_, ary);
809
810 // We just removed rows from the top of the screen, we need to update
811 // the cursor to match.
rginda35c456b2012-02-09 17:29:05 -0800812 cursor.row = Math.max(cursor.row - deltaRows, 0);
rgindac9bc5502012-01-18 11:48:44 -0800813 } else if (deltaRows > 0) {
814 // Screen got larger.
815
816 if (deltaRows <= this.scrollbackRows_.length) {
817 var scrollbackCount = Math.min(deltaRows, this.scrollbackRows_.length);
818 var rows = this.scrollbackRows_.splice(
819 this.scrollbackRows_.length - scrollbackCount, scrollbackCount);
820 this.screen_.unshiftRows(rows);
821 deltaRows -= scrollbackCount;
822 cursor.row += scrollbackCount;
823 }
824
825 if (deltaRows)
826 this.appendRows_(deltaRows);
827 }
828
rginda35c456b2012-02-09 17:29:05 -0800829 this.setVTScrollRegion(null, null);
rgindac9bc5502012-01-18 11:48:44 -0800830 this.restoreCursor(cursor);
rginda87b86462011-12-14 13:48:03 -0800831};
832
833/**
834 * Scroll the terminal to the top of the scrollback buffer.
835 */
836hterm.Terminal.prototype.scrollHome = function() {
837 this.scrollPort_.scrollRowToTop(0);
838};
839
840/**
841 * Scroll the terminal to the end.
842 */
843hterm.Terminal.prototype.scrollEnd = function() {
844 this.scrollPort_.scrollRowToBottom(this.getRowCount());
845};
846
847/**
848 * Scroll the terminal one page up (minus one line) relative to the current
849 * position.
850 */
851hterm.Terminal.prototype.scrollPageUp = function() {
852 var i = this.scrollPort_.getTopRowIndex();
853 this.scrollPort_.scrollRowToTop(i - this.screenSize.height + 1);
854};
855
856/**
857 * Scroll the terminal one page down (minus one line) relative to the current
858 * position.
859 */
860hterm.Terminal.prototype.scrollPageDown = function() {
861 var i = this.scrollPort_.getTopRowIndex();
862 this.scrollPort_.scrollRowToTop(i + this.screenSize.height - 1);
rginda8ba33642011-12-14 12:31:31 -0800863};
864
rgindac9bc5502012-01-18 11:48:44 -0800865/**
Robert Ginda40932892012-12-10 17:26:40 -0800866 * Clear primary screen, secondary screen, and the scrollback buffer.
867 */
868hterm.Terminal.prototype.wipeContents = function() {
869 this.scrollbackRows_.length = 0;
870 this.scrollPort_.resetCache();
871
872 [this.primaryScreen_, this.alternateScreen_].forEach(function(screen) {
873 var bottom = screen.getHeight();
874 if (bottom > 0) {
875 this.renumberRows_(0, bottom);
876 this.clearHome(screen);
877 }
878 }.bind(this));
879
880 this.syncCursorPosition_();
Andrew de los Reyes68e07802013-04-04 15:38:55 -0700881 this.scrollPort_.invalidate();
Robert Ginda40932892012-12-10 17:26:40 -0800882};
883
884/**
rgindac9bc5502012-01-18 11:48:44 -0800885 * Full terminal reset.
886 */
rginda87b86462011-12-14 13:48:03 -0800887hterm.Terminal.prototype.reset = function() {
rgindac9bc5502012-01-18 11:48:44 -0800888 this.clearAllTabStops();
889 this.setDefaultTabStops();
rginda9ea433c2012-03-16 11:57:00 -0700890
891 this.clearHome(this.primaryScreen_);
892 this.primaryScreen_.textAttributes.reset();
893
894 this.clearHome(this.alternateScreen_);
895 this.alternateScreen_.textAttributes.reset();
896
rgindab8bc8932012-04-27 12:45:03 -0700897 this.setCursorBlink(!!this.prefs_.get('cursor-blink'));
898
Robert Ginda92e18102013-03-14 13:56:37 -0700899 this.vt.reset();
900
rgindac9bc5502012-01-18 11:48:44 -0800901 this.softReset();
rginda87b86462011-12-14 13:48:03 -0800902};
903
rgindac9bc5502012-01-18 11:48:44 -0800904/**
905 * Soft terminal reset.
rgindab8bc8932012-04-27 12:45:03 -0700906 *
907 * Perform a soft reset to the default values listed in
908 * http://www.vt100.net/docs/vt510-rm/DECSTR#T5-9
rgindac9bc5502012-01-18 11:48:44 -0800909 */
rginda0f5c0292012-01-13 11:00:13 -0800910hterm.Terminal.prototype.softReset = function() {
rgindab8bc8932012-04-27 12:45:03 -0700911 // Reset terminal options to their default values.
rgindac9bc5502012-01-18 11:48:44 -0800912 this.options_ = new hterm.Options();
rgindaf522ce02012-04-17 17:49:17 -0700913
rgindab8bc8932012-04-27 12:45:03 -0700914 // Xterm also resets the color palette on soft reset, even though it doesn't
915 // seem to be documented anywhere.
rgindaf522ce02012-04-17 17:49:17 -0700916 this.primaryScreen_.textAttributes.resetColorPalette();
917 this.alternateScreen_.textAttributes.resetColorPalette();
918
rgindab8bc8932012-04-27 12:45:03 -0700919 // The xterm man page explicitly says this will happen on soft reset.
920 this.setVTScrollRegion(null, null);
921
922 // Xterm also shows the cursor on soft reset, but does not alter the blink
923 // state.
rgindaa19afe22012-01-25 15:40:22 -0800924 this.setCursorVisible(true);
rginda0f5c0292012-01-13 11:00:13 -0800925};
926
rgindac9bc5502012-01-18 11:48:44 -0800927/**
928 * Move the cursor forward to the next tab stop, or to the last column
929 * if no more tab stops are set.
930 */
931hterm.Terminal.prototype.forwardTabStop = function() {
932 var column = this.screen_.cursorPosition.column;
933
934 for (var i = 0; i < this.tabStops_.length; i++) {
935 if (this.tabStops_[i] > column) {
936 this.setCursorColumn(this.tabStops_[i]);
937 return;
938 }
939 }
940
David Benjamin66e954d2012-05-05 21:08:12 -0400941 // xterm does not clear the overflow flag on HT or CHT.
942 var overflow = this.screen_.cursorPosition.overflow;
rgindac9bc5502012-01-18 11:48:44 -0800943 this.setCursorColumn(this.screenSize.width - 1);
David Benjamin66e954d2012-05-05 21:08:12 -0400944 this.screen_.cursorPosition.overflow = overflow;
rginda0f5c0292012-01-13 11:00:13 -0800945};
946
rgindac9bc5502012-01-18 11:48:44 -0800947/**
948 * Move the cursor backward to the previous tab stop, or to the first column
949 * if no previous tab stops are set.
950 */
951hterm.Terminal.prototype.backwardTabStop = function() {
952 var column = this.screen_.cursorPosition.column;
953
954 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
955 if (this.tabStops_[i] < column) {
956 this.setCursorColumn(this.tabStops_[i]);
957 return;
958 }
959 }
960
961 this.setCursorColumn(1);
rginda0f5c0292012-01-13 11:00:13 -0800962};
963
rgindac9bc5502012-01-18 11:48:44 -0800964/**
965 * Set a tab stop at the given column.
966 *
967 * @param {int} column Zero based column.
968 */
969hterm.Terminal.prototype.setTabStop = function(column) {
970 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
971 if (this.tabStops_[i] == column)
972 return;
973
974 if (this.tabStops_[i] < column) {
975 this.tabStops_.splice(i + 1, 0, column);
976 return;
977 }
978 }
979
980 this.tabStops_.splice(0, 0, column);
rginda87b86462011-12-14 13:48:03 -0800981};
982
rgindac9bc5502012-01-18 11:48:44 -0800983/**
984 * Clear the tab stop at the current cursor position.
985 *
986 * No effect if there is no tab stop at the current cursor position.
987 */
988hterm.Terminal.prototype.clearTabStopAtCursor = function() {
989 var column = this.screen_.cursorPosition.column;
990
991 var i = this.tabStops_.indexOf(column);
992 if (i == -1)
993 return;
994
995 this.tabStops_.splice(i, 1);
996};
997
998/**
999 * Clear all tab stops.
1000 */
1001hterm.Terminal.prototype.clearAllTabStops = function() {
1002 this.tabStops_.length = 0;
David Benjamin66e954d2012-05-05 21:08:12 -04001003 this.defaultTabStops = false;
rgindac9bc5502012-01-18 11:48:44 -08001004};
1005
1006/**
1007 * Set up the default tab stops, starting from a given column.
1008 *
1009 * This sets a tabstop every (column % this.tabWidth) column, starting
David Benjamin66e954d2012-05-05 21:08:12 -04001010 * from the specified column, or 0 if no column is provided. It also flags
1011 * future resizes to set them up.
rgindac9bc5502012-01-18 11:48:44 -08001012 *
1013 * This does not clear the existing tab stops first, use clearAllTabStops
1014 * for that.
1015 *
1016 * @param {int} opt_start Optional starting zero based starting column, useful
1017 * for filling out missing tab stops when the terminal is resized.
1018 */
1019hterm.Terminal.prototype.setDefaultTabStops = function(opt_start) {
1020 var start = opt_start || 0;
1021 var w = this.tabWidth;
David Benjamin66e954d2012-05-05 21:08:12 -04001022 // Round start up to a default tab stop.
1023 start = start - 1 - ((start - 1) % w) + w;
1024 for (var i = start; i < this.screenSize.width; i += w) {
1025 this.setTabStop(i);
rgindac9bc5502012-01-18 11:48:44 -08001026 }
David Benjamin66e954d2012-05-05 21:08:12 -04001027
1028 this.defaultTabStops = true;
rginda87b86462011-12-14 13:48:03 -08001029};
1030
rginda6d397402012-01-17 10:58:29 -08001031/**
rginda8ba33642011-12-14 12:31:31 -08001032 * Interpret a sequence of characters.
1033 *
1034 * Incomplete escape sequences are buffered until the next call.
1035 *
1036 * @param {string} str Sequence of characters to interpret or pass through.
1037 */
1038hterm.Terminal.prototype.interpret = function(str) {
rginda0f5c0292012-01-13 11:00:13 -08001039 this.vt.interpret(str);
rginda8ba33642011-12-14 12:31:31 -08001040 this.scheduleSyncCursorPosition_();
1041};
1042
1043/**
1044 * Take over the given DIV for use as the terminal display.
1045 *
1046 * @param {HTMLDivElement} div The div to use as the terminal display.
1047 */
1048hterm.Terminal.prototype.decorate = function(div) {
rginda87b86462011-12-14 13:48:03 -08001049 this.div_ = div;
1050
rginda8ba33642011-12-14 12:31:31 -08001051 this.scrollPort_.decorate(div);
rginda30f20f62012-04-05 16:36:19 -07001052 this.scrollPort_.setBackgroundImage(this.prefs_.get('background-image'));
Philip Douglass959b49d2012-05-30 13:29:29 -04001053 this.scrollPort_.setBackgroundSize(this.prefs_.get('background-size'));
1054 this.scrollPort_.setBackgroundPosition(
1055 this.prefs_.get('background-position'));
rginda30f20f62012-04-05 16:36:19 -07001056
rginda0918b652012-04-04 11:26:24 -07001057 this.div_.focus = this.focus.bind(this);
rgindaf7521392012-02-28 17:20:34 -08001058
rginda9f5222b2012-03-05 11:53:28 -08001059 this.setFontSize(this.prefs_.get('font-size'));
1060 this.syncFontFamily();
rgindaa19afe22012-01-25 15:40:22 -08001061
David Reveman8f552492012-03-28 12:18:41 -04001062 this.setScrollbarVisible(this.prefs_.get('scrollbar-visible'));
1063
rginda8ba33642011-12-14 12:31:31 -08001064 this.document_ = this.scrollPort_.getDocument();
1065
rginda4bba5e12012-06-20 16:15:30 -07001066 this.document_.body.oncontextmenu = function() { return false };
1067
1068 var onMouse = this.onMouse_.bind(this);
Toni Barzic0bfa8922013-11-22 11:18:35 -08001069 var screenNode = this.scrollPort_.getScreenNode();
1070 screenNode.addEventListener('mousedown', onMouse);
1071 screenNode.addEventListener('mouseup', onMouse);
1072 screenNode.addEventListener('mousemove', onMouse);
rginda4bba5e12012-06-20 16:15:30 -07001073 this.scrollPort_.onScrollWheel = onMouse;
1074
Toni Barzic0bfa8922013-11-22 11:18:35 -08001075 screenNode.addEventListener(
rginda8e92a692012-05-20 19:37:20 -07001076 'focus', this.onFocusChange_.bind(this, true));
Toni Barzic0bfa8922013-11-22 11:18:35 -08001077 screenNode.addEventListener(
rginda8e92a692012-05-20 19:37:20 -07001078 'blur', this.onFocusChange_.bind(this, false));
1079
1080 var style = this.document_.createElement('style');
1081 style.textContent =
1082 ('.cursor-node[focus="false"] {' +
1083 ' box-sizing: border-box;' +
1084 ' background-color: transparent !important;' +
1085 ' border-width: 2px;' +
1086 ' border-style: solid;' +
Ricky Liang48f05cb2013-12-31 23:35:29 +08001087 '}' +
1088 '.wc-node {' +
1089 ' display: inline-block;' +
1090 ' text-align: center;' +
1091 ' width: ' + this.scrollPort_.characterSize.width * 2 + 'px;' +
rginda8e92a692012-05-20 19:37:20 -07001092 '}');
1093 this.document_.head.appendChild(style);
1094
Ricky Liang48f05cb2013-12-31 23:35:29 +08001095 var styleSheets = this.document_.styleSheets;
1096 var cssRules = styleSheets[styleSheets.length - 1].cssRules;
1097 this.wcCssRule_ = cssRules[cssRules.length - 1];
1098
rginda8ba33642011-12-14 12:31:31 -08001099 this.cursorNode_ = this.document_.createElement('div');
rginda8e92a692012-05-20 19:37:20 -07001100 this.cursorNode_.className = 'cursor-node';
rginda8ba33642011-12-14 12:31:31 -08001101 this.cursorNode_.style.cssText =
1102 ('position: absolute;' +
rginda87b86462011-12-14 13:48:03 -08001103 'top: -99px;' +
1104 'display: block;' +
rginda35c456b2012-02-09 17:29:05 -08001105 'width: ' + this.scrollPort_.characterSize.width + 'px;' +
1106 'height: ' + this.scrollPort_.characterSize.height + 'px;' +
rginda8e92a692012-05-20 19:37:20 -07001107 '-webkit-transition: opacity, background-color 100ms linear;');
Robert Gindafb1be6a2013-12-11 11:56:22 -08001108
rginda8e92a692012-05-20 19:37:20 -07001109 this.setCursorColor(this.prefs_.get('cursor-color'));
Robert Gindafb1be6a2013-12-11 11:56:22 -08001110 this.setCursorBlink(!!this.prefs_.get('cursor-blink'));
1111 this.restyleCursor_();
rgindad5613292012-06-19 15:40:37 -07001112
rginda8ba33642011-12-14 12:31:31 -08001113 this.document_.body.appendChild(this.cursorNode_);
1114
rgindad5613292012-06-19 15:40:37 -07001115 // When 'enableMouseDragScroll' is off we reposition this element directly
1116 // under the mouse cursor after a click. This makes Chrome associate
1117 // subsequent mousemove events with the scroll-blocker. Since the
1118 // scroll-blocker is a peer (not a child) of the scrollport, the mousemove
1119 // events do not cause the scrollport to scroll.
1120 //
1121 // It's a hack, but it's the cleanest way I could find.
1122 this.scrollBlockerNode_ = this.document_.createElement('div');
1123 this.scrollBlockerNode_.style.cssText =
1124 ('position: absolute;' +
1125 'top: -99px;' +
1126 'display: block;' +
1127 'width: 10px;' +
1128 'height: 10px;');
1129 this.document_.body.appendChild(this.scrollBlockerNode_);
1130
1131 var onMouse = this.onMouse_.bind(this);
1132 this.scrollPort_.onScrollWheel = onMouse;
1133 ['mousedown', 'mouseup', 'mousemove', 'click', 'dblclick',
1134 ].forEach(function(event) {
1135 this.scrollBlockerNode_.addEventListener(event, onMouse);
1136 this.cursorNode_.addEventListener(event, onMouse);
1137 this.document_.addEventListener(event, onMouse);
1138 }.bind(this));
1139
1140 this.cursorNode_.addEventListener('mousedown', function() {
1141 setTimeout(this.focus.bind(this));
1142 }.bind(this));
1143
rginda8ba33642011-12-14 12:31:31 -08001144 this.setReverseVideo(false);
rginda87b86462011-12-14 13:48:03 -08001145
rginda87b86462011-12-14 13:48:03 -08001146 this.scrollPort_.focus();
rginda6d397402012-01-17 10:58:29 -08001147 this.scrollPort_.scheduleRedraw();
rginda87b86462011-12-14 13:48:03 -08001148};
1149
rginda0918b652012-04-04 11:26:24 -07001150/**
1151 * Return the HTML document that contains the terminal DOM nodes.
1152 */
rginda87b86462011-12-14 13:48:03 -08001153hterm.Terminal.prototype.getDocument = function() {
1154 return this.document_;
rginda8ba33642011-12-14 12:31:31 -08001155};
1156
1157/**
rginda0918b652012-04-04 11:26:24 -07001158 * Focus the terminal.
1159 */
1160hterm.Terminal.prototype.focus = function() {
1161 this.scrollPort_.focus();
1162};
1163
1164/**
rginda8ba33642011-12-14 12:31:31 -08001165 * Return the HTML Element for a given row index.
1166 *
1167 * This is a method from the RowProvider interface. The ScrollPort uses
1168 * it to fetch rows on demand as they are scrolled into view.
1169 *
1170 * TODO(rginda): Consider saving scrollback rows as (HTML source, text content)
1171 * pairs to conserve memory.
1172 *
1173 * @param {integer} index The zero-based row index, measured relative to the
1174 * start of the scrollback buffer. On-screen rows will always have the
1175 * largest indicies.
1176 * @return {HTMLElement} The 'x-row' element containing for the requested row.
1177 */
1178hterm.Terminal.prototype.getRowNode = function(index) {
1179 if (index < this.scrollbackRows_.length)
1180 return this.scrollbackRows_[index];
1181
1182 var screenIndex = index - this.scrollbackRows_.length;
1183 return this.screen_.rowsArray[screenIndex];
1184};
1185
1186/**
1187 * Return the text content for a given range of rows.
1188 *
1189 * This is a method from the RowProvider interface. The ScrollPort uses
1190 * it to fetch text content on demand when the user attempts to copy their
1191 * selection to the clipboard.
1192 *
1193 * @param {integer} start The zero-based row index to start from, measured
1194 * relative to the start of the scrollback buffer. On-screen rows will
1195 * always have the largest indicies.
1196 * @param {integer} end The zero-based row index to end on, measured
1197 * relative to the start of the scrollback buffer.
1198 * @return {string} A single string containing the text value of the range of
1199 * rows. Lines will be newline delimited, with no trailing newline.
1200 */
1201hterm.Terminal.prototype.getRowsText = function(start, end) {
1202 var ary = [];
1203 for (var i = start; i < end; i++) {
1204 var node = this.getRowNode(i);
1205 ary.push(node.textContent);
rgindaa09e7332012-08-17 12:49:51 -07001206 if (i < end - 1 && !node.getAttribute('line-overflow'))
1207 ary.push('\n');
rginda8ba33642011-12-14 12:31:31 -08001208 }
1209
rgindaa09e7332012-08-17 12:49:51 -07001210 return ary.join('');
rginda8ba33642011-12-14 12:31:31 -08001211};
1212
1213/**
1214 * Return the text content for a given row.
1215 *
1216 * This is a method from the RowProvider interface. The ScrollPort uses
1217 * it to fetch text content on demand when the user attempts to copy their
1218 * selection to the clipboard.
1219 *
1220 * @param {integer} index The zero-based row index to return, measured
1221 * relative to the start of the scrollback buffer. On-screen rows will
1222 * always have the largest indicies.
1223 * @return {string} A string containing the text value of the selected row.
1224 */
1225hterm.Terminal.prototype.getRowText = function(index) {
1226 var node = this.getRowNode(index);
rginda87b86462011-12-14 13:48:03 -08001227 return node.textContent;
rginda8ba33642011-12-14 12:31:31 -08001228};
1229
1230/**
1231 * Return the total number of rows in the addressable screen and in the
1232 * scrollback buffer of this terminal.
1233 *
1234 * This is a method from the RowProvider interface. The ScrollPort uses
1235 * it to compute the size of the scrollbar.
1236 *
1237 * @return {integer} The number of rows in this terminal.
1238 */
1239hterm.Terminal.prototype.getRowCount = function() {
1240 return this.scrollbackRows_.length + this.screen_.rowsArray.length;
1241};
1242
1243/**
1244 * Create DOM nodes for new rows and append them to the end of the terminal.
1245 *
1246 * This is the only correct way to add a new DOM node for a row. Notice that
1247 * the new row is appended to the bottom of the list of rows, and does not
1248 * require renumbering (of the rowIndex property) of previous rows.
1249 *
1250 * If you think you want a new blank row somewhere in the middle of the
1251 * terminal, look into moveRows_().
1252 *
1253 * This method does not pay attention to vtScrollTop/Bottom, since you should
1254 * be using moveRows() in cases where they would matter.
1255 *
1256 * The cursor will be positioned at column 0 of the first inserted line.
1257 */
1258hterm.Terminal.prototype.appendRows_ = function(count) {
1259 var cursorRow = this.screen_.rowsArray.length;
1260 var offset = this.scrollbackRows_.length + cursorRow;
1261 for (var i = 0; i < count; i++) {
1262 var row = this.document_.createElement('x-row');
1263 row.appendChild(this.document_.createTextNode(''));
1264 row.rowIndex = offset + i;
1265 this.screen_.pushRow(row);
1266 }
1267
1268 var extraRows = this.screen_.rowsArray.length - this.screenSize.height;
1269 if (extraRows > 0) {
1270 var ary = this.screen_.shiftRows(extraRows);
1271 Array.prototype.push.apply(this.scrollbackRows_, ary);
Robert Ginda36c5aa62012-10-15 11:17:47 -07001272 if (this.scrollPort_.isScrolledEnd)
1273 this.scheduleScrollDown_();
rginda8ba33642011-12-14 12:31:31 -08001274 }
1275
1276 if (cursorRow >= this.screen_.rowsArray.length)
1277 cursorRow = this.screen_.rowsArray.length - 1;
1278
rginda87b86462011-12-14 13:48:03 -08001279 this.setAbsoluteCursorPosition(cursorRow, 0);
rginda8ba33642011-12-14 12:31:31 -08001280};
1281
1282/**
1283 * Relocate rows from one part of the addressable screen to another.
1284 *
1285 * This is used to recycle rows during VT scrolls (those which are driven
1286 * by VT commands, rather than by the user manipulating the scrollbar.)
1287 *
1288 * In this case, the blank lines scrolled into the scroll region are made of
1289 * the nodes we scrolled off. These have their rowIndex properties carefully
1290 * renumbered so as not to confuse the ScrollPort.
rginda8ba33642011-12-14 12:31:31 -08001291 */
1292hterm.Terminal.prototype.moveRows_ = function(fromIndex, count, toIndex) {
1293 var ary = this.screen_.removeRows(fromIndex, count);
1294 this.screen_.insertRows(toIndex, ary);
1295
1296 var start, end;
1297 if (fromIndex < toIndex) {
1298 start = fromIndex;
rginda87b86462011-12-14 13:48:03 -08001299 end = toIndex + count;
rginda8ba33642011-12-14 12:31:31 -08001300 } else {
1301 start = toIndex;
rginda87b86462011-12-14 13:48:03 -08001302 end = fromIndex + count;
rginda8ba33642011-12-14 12:31:31 -08001303 }
1304
1305 this.renumberRows_(start, end);
rginda2312fff2012-01-05 16:20:52 -08001306 this.scrollPort_.scheduleInvalidate();
rginda8ba33642011-12-14 12:31:31 -08001307};
1308
1309/**
1310 * Renumber the rowIndex property of the given range of rows.
1311 *
1312 * The start and end indicies are relative to the screen, not the scrollback.
1313 * Rows in the scrollback buffer cannot be renumbered. Since they are not
rginda2312fff2012-01-05 16:20:52 -08001314 * addressable (you can't delete them, scroll them, etc), you should have
rginda8ba33642011-12-14 12:31:31 -08001315 * no need to renumber scrollback rows.
1316 */
Robert Ginda40932892012-12-10 17:26:40 -08001317hterm.Terminal.prototype.renumberRows_ = function(start, end, opt_screen) {
1318 var screen = opt_screen || this.screen_;
1319
rginda8ba33642011-12-14 12:31:31 -08001320 var offset = this.scrollbackRows_.length;
1321 for (var i = start; i < end; i++) {
Robert Ginda40932892012-12-10 17:26:40 -08001322 screen.rowsArray[i].rowIndex = offset + i;
rginda8ba33642011-12-14 12:31:31 -08001323 }
1324};
1325
1326/**
1327 * Print a string to the terminal.
1328 *
1329 * This respects the current insert and wraparound modes. It will add new lines
1330 * to the end of the terminal, scrolling off the top into the scrollback buffer
1331 * if necessary.
1332 *
1333 * The string is *not* parsed for escape codes. Use the interpret() method if
1334 * that's what you're after.
1335 *
1336 * @param{string} str The string to print.
1337 */
1338hterm.Terminal.prototype.print = function(str) {
rgindaa9abdd82012-08-06 18:05:09 -07001339 var startOffset = 0;
rginda2312fff2012-01-05 16:20:52 -08001340
Ricky Liang48f05cb2013-12-31 23:35:29 +08001341 var strWidth = lib.wc.strWidth(str);
1342
1343 while (startOffset < strWidth) {
rgindaa09e7332012-08-17 12:49:51 -07001344 if (this.options_.wraparound && this.screen_.cursorPosition.overflow) {
1345 this.screen_.commitLineOverflow();
rginda35c456b2012-02-09 17:29:05 -08001346 this.newLine();
rgindaa09e7332012-08-17 12:49:51 -07001347 }
rgindaa19afe22012-01-25 15:40:22 -08001348
Ricky Liang48f05cb2013-12-31 23:35:29 +08001349 var count = strWidth - startOffset;
rgindaa9abdd82012-08-06 18:05:09 -07001350 var didOverflow = false;
1351 var substr;
rgindaa19afe22012-01-25 15:40:22 -08001352
rgindaa9abdd82012-08-06 18:05:09 -07001353 if (this.screen_.cursorPosition.column + count >= this.screenSize.width) {
1354 didOverflow = true;
1355 count = this.screenSize.width - this.screen_.cursorPosition.column;
1356 }
rgindaa19afe22012-01-25 15:40:22 -08001357
rgindaa9abdd82012-08-06 18:05:09 -07001358 if (didOverflow && !this.options_.wraparound) {
1359 // If the string overflowed the line but wraparound is off, then the
1360 // last printed character should be the last of the string.
1361 // TODO: This will add to our problems with multibyte UTF-16 characters.
Ricky Liang48f05cb2013-12-31 23:35:29 +08001362 substr = lib.wc.substr(str, startOffset, count - 1) +
1363 lib.wc.substr(str, strWidth - 1);
1364 count = strWidth;
rgindaa9abdd82012-08-06 18:05:09 -07001365 } else {
Ricky Liang48f05cb2013-12-31 23:35:29 +08001366 substr = lib.wc.substr(str, startOffset, count);
rgindaa9abdd82012-08-06 18:05:09 -07001367 }
rgindaa19afe22012-01-25 15:40:22 -08001368
Ricky Liang48f05cb2013-12-31 23:35:29 +08001369 var tokens = hterm.TextAttributes.splitWidecharString(substr);
1370 for (var i = 0; i < tokens.length; i++) {
1371 if (tokens[i].wcNode)
1372 this.screen_.textAttributes.wcNode = true;
1373
1374 if (this.options_.insertMode) {
1375 this.screen_.insertString(tokens[i].str);
1376 } else {
1377 this.screen_.overwriteString(tokens[i].str);
1378 }
1379 this.screen_.textAttributes.wcNode = false;
rgindaa9abdd82012-08-06 18:05:09 -07001380 }
1381
1382 this.screen_.maybeClipCurrentRow();
1383 startOffset += count;
rgindaa19afe22012-01-25 15:40:22 -08001384 }
rginda8ba33642011-12-14 12:31:31 -08001385
1386 this.scheduleSyncCursorPosition_();
rginda0f5c0292012-01-13 11:00:13 -08001387
rginda9f5222b2012-03-05 11:53:28 -08001388 if (this.scrollOnOutput_)
rginda0f5c0292012-01-13 11:00:13 -08001389 this.scrollPort_.scrollRowToBottom(this.getRowCount());
rginda8ba33642011-12-14 12:31:31 -08001390};
1391
1392/**
rginda87b86462011-12-14 13:48:03 -08001393 * Set the VT scroll region.
1394 *
rginda87b86462011-12-14 13:48:03 -08001395 * This also resets the cursor position to the absolute (0, 0) position, since
1396 * that's what xterm appears to do.
1397 *
Robert Ginda5b9fbe62013-10-30 14:05:53 -07001398 * Setting the scroll region to the full height of the terminal will clear
1399 * the scroll region. This is *NOT* what most terminals do. We're explicitly
1400 * going "off-spec" here because it makes `screen` and `tmux` overflow into the
1401 * local scrollback buffer, which means the scrollbars and shift-pgup/pgdn
1402 * continue to work as most users would expect.
1403 *
rginda87b86462011-12-14 13:48:03 -08001404 * @param {integer} scrollTop The zero-based top of the scroll region.
1405 * @param {integer} scrollBottom The zero-based bottom of the scroll region,
1406 * inclusive.
1407 */
1408hterm.Terminal.prototype.setVTScrollRegion = function(scrollTop, scrollBottom) {
Robert Ginda5b9fbe62013-10-30 14:05:53 -07001409 if (scrollTop == 0 && scrollBottom == this.screenSize.height - 1) {
Robert Ginda43684e22013-11-25 14:18:52 -08001410 this.vtScrollTop_ = null;
1411 this.vtScrollBottom_ = null;
Robert Ginda5b9fbe62013-10-30 14:05:53 -07001412 } else {
1413 this.vtScrollTop_ = scrollTop;
1414 this.vtScrollBottom_ = scrollBottom;
1415 }
rginda87b86462011-12-14 13:48:03 -08001416};
1417
1418/**
rginda8ba33642011-12-14 12:31:31 -08001419 * Return the top row index according to the VT.
1420 *
1421 * This will return 0 unless the terminal has been told to restrict scrolling
1422 * to some lower row. It is used for some VT cursor positioning and scrolling
1423 * commands.
1424 *
1425 * @return {integer} The topmost row in the terminal's scroll region.
1426 */
1427hterm.Terminal.prototype.getVTScrollTop = function() {
1428 if (this.vtScrollTop_ != null)
1429 return this.vtScrollTop_;
1430
1431 return 0;
rginda87b86462011-12-14 13:48:03 -08001432};
rginda8ba33642011-12-14 12:31:31 -08001433
1434/**
1435 * Return the bottom row index according to the VT.
1436 *
1437 * This will return the height of the terminal unless the it has been told to
1438 * restrict scrolling to some higher row. It is used for some VT cursor
1439 * positioning and scrolling commands.
1440 *
1441 * @return {integer} The bottommost row in the terminal's scroll region.
1442 */
1443hterm.Terminal.prototype.getVTScrollBottom = function() {
1444 if (this.vtScrollBottom_ != null)
1445 return this.vtScrollBottom_;
1446
rginda87b86462011-12-14 13:48:03 -08001447 return this.screenSize.height - 1;
rginda8ba33642011-12-14 12:31:31 -08001448}
1449
1450/**
1451 * Process a '\n' character.
1452 *
1453 * If the cursor is on the final row of the terminal this will append a new
1454 * blank row to the screen and scroll the topmost row into the scrollback
1455 * buffer.
1456 *
1457 * Otherwise, this moves the cursor to column zero of the next row.
1458 */
1459hterm.Terminal.prototype.newLine = function() {
Robert Ginda9937abc2013-07-25 16:09:23 -07001460 var cursorAtEndOfScreen = (this.screen_.cursorPosition.row ==
1461 this.screen_.rowsArray.length - 1);
1462
1463 if (this.vtScrollBottom_ != null) {
1464 // A VT Scroll region is active, we never append new rows.
1465 if (this.screen_.cursorPosition.row == this.vtScrollBottom_) {
1466 // We're at the end of the VT Scroll Region, perform a VT scroll.
1467 this.vtScrollUp(1);
1468 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, 0);
1469 } else if (cursorAtEndOfScreen) {
1470 // We're at the end of the screen, the only thing to do is put the
1471 // cursor to column 0.
1472 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, 0);
1473 } else {
1474 // Anywhere else, advance the cursor row, and reset the column.
1475 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row + 1, 0);
1476 }
1477 } else if (cursorAtEndOfScreen) {
Robert Ginda1b06b372013-07-19 15:22:51 -07001478 // We're at the end of the screen. Append a new row to the terminal,
1479 // shifting the top row into the scrollback.
1480 this.appendRows_(1);
rginda8ba33642011-12-14 12:31:31 -08001481 } else {
rginda87b86462011-12-14 13:48:03 -08001482 // Anywhere else in the screen just moves the cursor.
1483 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row + 1, 0);
rginda8ba33642011-12-14 12:31:31 -08001484 }
1485};
1486
1487/**
1488 * Like newLine(), except maintain the cursor column.
1489 */
1490hterm.Terminal.prototype.lineFeed = function() {
1491 var column = this.screen_.cursorPosition.column;
1492 this.newLine();
1493 this.setCursorColumn(column);
1494};
1495
1496/**
rginda87b86462011-12-14 13:48:03 -08001497 * If autoCarriageReturn is set then newLine(), else lineFeed().
1498 */
1499hterm.Terminal.prototype.formFeed = function() {
1500 if (this.options_.autoCarriageReturn) {
1501 this.newLine();
1502 } else {
1503 this.lineFeed();
1504 }
1505};
1506
1507/**
1508 * Move the cursor up one row, possibly inserting a blank line.
1509 *
1510 * The cursor column is not changed.
1511 */
1512hterm.Terminal.prototype.reverseLineFeed = function() {
1513 var scrollTop = this.getVTScrollTop();
1514 var currentRow = this.screen_.cursorPosition.row;
1515
1516 if (currentRow == scrollTop) {
1517 this.insertLines(1);
1518 } else {
1519 this.setAbsoluteCursorRow(currentRow - 1);
1520 }
1521};
1522
1523/**
rginda8ba33642011-12-14 12:31:31 -08001524 * Replace all characters to the left of the current cursor with the space
1525 * character.
1526 *
1527 * TODO(rginda): This should probably *remove* the characters (not just replace
1528 * with a space) if there are no characters at or beyond the current cursor
Robert Gindaf2547f12012-10-25 20:36:21 -07001529 * position.
rginda8ba33642011-12-14 12:31:31 -08001530 */
1531hterm.Terminal.prototype.eraseToLeft = function() {
rginda87b86462011-12-14 13:48:03 -08001532 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001533 this.setCursorColumn(0);
rgindacbbd7482012-06-13 15:06:16 -07001534 this.screen_.overwriteString(lib.f.getWhitespace(cursor.column + 1));
rginda87b86462011-12-14 13:48:03 -08001535 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001536};
1537
1538/**
David Benjamin684a9b72012-05-01 17:19:58 -04001539 * Erase a given number of characters to the right of the cursor.
rginda8ba33642011-12-14 12:31:31 -08001540 *
1541 * The cursor position is unchanged.
1542 *
Robert Gindaf2547f12012-10-25 20:36:21 -07001543 * If the current background color is not the default background color this
1544 * will insert spaces rather than delete. This is unfortunate because the
1545 * trailing space will affect text selection, but it's difficult to come up
1546 * with a way to style empty space that wouldn't trip up the hterm.Screen
1547 * code.
Robert Gindacd5637d2013-10-30 14:59:10 -07001548 *
1549 * eraseToRight is ignored in the presence of a cursor overflow. This deviates
1550 * from xterm, but agrees with gnome-terminal and konsole, xfce4-terminal. See
1551 * crbug.com/232390 for details.
rginda8ba33642011-12-14 12:31:31 -08001552 */
1553hterm.Terminal.prototype.eraseToRight = function(opt_count) {
Robert Gindacd5637d2013-10-30 14:59:10 -07001554 if (this.screen_.cursorPosition.overflow)
1555 return;
1556
Robert Ginda7fd57082012-09-25 14:41:47 -07001557 var maxCount = this.screenSize.width - this.screen_.cursorPosition.column;
1558 var count = opt_count ? Math.min(opt_count, maxCount) : maxCount;
Robert Gindaf2547f12012-10-25 20:36:21 -07001559
1560 if (this.screen_.textAttributes.background ===
1561 this.screen_.textAttributes.DEFAULT_COLOR) {
1562 var cursorRow = this.screen_.rowsArray[this.screen_.cursorPosition.row];
Ricky Liang48f05cb2013-12-31 23:35:29 +08001563 if (hterm.TextAttributes.nodeWidth(cursorRow) <=
Robert Gindaf2547f12012-10-25 20:36:21 -07001564 this.screen_.cursorPosition.column + count) {
1565 this.screen_.deleteChars(count);
1566 this.clearCursorOverflow();
1567 return;
1568 }
1569 }
1570
rginda87b86462011-12-14 13:48:03 -08001571 var cursor = this.saveCursor();
Robert Ginda7fd57082012-09-25 14:41:47 -07001572 this.screen_.overwriteString(lib.f.getWhitespace(count));
rginda87b86462011-12-14 13:48:03 -08001573 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001574 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001575};
1576
1577/**
1578 * Erase the current line.
1579 *
1580 * The cursor position is unchanged.
rginda8ba33642011-12-14 12:31:31 -08001581 */
1582hterm.Terminal.prototype.eraseLine = function() {
rginda87b86462011-12-14 13:48:03 -08001583 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001584 this.screen_.clearCursorRow();
rginda87b86462011-12-14 13:48:03 -08001585 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001586 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001587};
1588
1589/**
David Benjamina08d78f2012-05-05 00:28:49 -04001590 * Erase all characters from the start of the screen to the current cursor
1591 * position, regardless of scroll region.
rginda8ba33642011-12-14 12:31:31 -08001592 *
1593 * The cursor position is unchanged.
rginda8ba33642011-12-14 12:31:31 -08001594 */
1595hterm.Terminal.prototype.eraseAbove = function() {
rginda87b86462011-12-14 13:48:03 -08001596 var cursor = this.saveCursor();
1597
1598 this.eraseToLeft();
rginda8ba33642011-12-14 12:31:31 -08001599
David Benjamina08d78f2012-05-05 00:28:49 -04001600 for (var i = 0; i < cursor.row; i++) {
rginda87b86462011-12-14 13:48:03 -08001601 this.setAbsoluteCursorPosition(i, 0);
rginda8ba33642011-12-14 12:31:31 -08001602 this.screen_.clearCursorRow();
1603 }
1604
rginda87b86462011-12-14 13:48:03 -08001605 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001606 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001607};
1608
1609/**
1610 * Erase all characters from the current cursor position to the end of the
David Benjamina08d78f2012-05-05 00:28:49 -04001611 * screen, regardless of scroll region.
rginda8ba33642011-12-14 12:31:31 -08001612 *
1613 * The cursor position is unchanged.
rginda8ba33642011-12-14 12:31:31 -08001614 */
1615hterm.Terminal.prototype.eraseBelow = function() {
rginda87b86462011-12-14 13:48:03 -08001616 var cursor = this.saveCursor();
1617
1618 this.eraseToRight();
rginda8ba33642011-12-14 12:31:31 -08001619
David Benjamina08d78f2012-05-05 00:28:49 -04001620 var bottom = this.screenSize.height - 1;
rginda87b86462011-12-14 13:48:03 -08001621 for (var i = cursor.row + 1; i <= bottom; i++) {
1622 this.setAbsoluteCursorPosition(i, 0);
rginda8ba33642011-12-14 12:31:31 -08001623 this.screen_.clearCursorRow();
1624 }
1625
rginda87b86462011-12-14 13:48:03 -08001626 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001627 this.clearCursorOverflow();
rginda87b86462011-12-14 13:48:03 -08001628};
1629
1630/**
1631 * Fill the terminal with a given character.
1632 *
1633 * This methods does not respect the VT scroll region.
1634 *
1635 * @param {string} ch The character to use for the fill.
1636 */
1637hterm.Terminal.prototype.fill = function(ch) {
1638 var cursor = this.saveCursor();
1639
1640 this.setAbsoluteCursorPosition(0, 0);
1641 for (var row = 0; row < this.screenSize.height; row++) {
1642 for (var col = 0; col < this.screenSize.width; col++) {
1643 this.setAbsoluteCursorPosition(row, col);
1644 this.screen_.overwriteString(ch);
1645 }
1646 }
1647
1648 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001649};
1650
1651/**
rginda9ea433c2012-03-16 11:57:00 -07001652 * Erase the entire display and leave the cursor at (0, 0).
rginda8ba33642011-12-14 12:31:31 -08001653 *
rginda9ea433c2012-03-16 11:57:00 -07001654 * This does not respect the scroll region.
1655 *
1656 * @param {hterm.Screen} opt_screen Optional screen to operate on. Defaults
1657 * to the current screen.
rginda8ba33642011-12-14 12:31:31 -08001658 */
rginda9ea433c2012-03-16 11:57:00 -07001659hterm.Terminal.prototype.clearHome = function(opt_screen) {
1660 var screen = opt_screen || this.screen_;
1661 var bottom = screen.getHeight();
rginda8ba33642011-12-14 12:31:31 -08001662
rginda11057d52012-04-25 12:29:56 -07001663 if (bottom == 0) {
1664 // Empty screen, nothing to do.
1665 return;
1666 }
1667
rgindae4d29232012-01-19 10:47:13 -08001668 for (var i = 0; i < bottom; i++) {
rginda9ea433c2012-03-16 11:57:00 -07001669 screen.setCursorPosition(i, 0);
1670 screen.clearCursorRow();
rginda8ba33642011-12-14 12:31:31 -08001671 }
1672
rginda9ea433c2012-03-16 11:57:00 -07001673 screen.setCursorPosition(0, 0);
1674};
1675
1676/**
1677 * Erase the entire display without changing the cursor position.
1678 *
1679 * The cursor position is unchanged. This does not respect the scroll
1680 * region.
1681 *
1682 * @param {hterm.Screen} opt_screen Optional screen to operate on. Defaults
1683 * to the current screen.
rginda9ea433c2012-03-16 11:57:00 -07001684 */
1685hterm.Terminal.prototype.clear = function(opt_screen) {
1686 var screen = opt_screen || this.screen_;
1687 var cursor = screen.cursorPosition.clone();
1688 this.clearHome(screen);
1689 screen.setCursorPosition(cursor.row, cursor.column);
rginda8ba33642011-12-14 12:31:31 -08001690};
1691
1692/**
1693 * VT command to insert lines at the current cursor row.
1694 *
1695 * This respects the current scroll region. Rows pushed off the bottom are
1696 * lost (they won't show up in the scrollback buffer).
1697 *
rginda8ba33642011-12-14 12:31:31 -08001698 * @param {integer} count The number of lines to insert.
1699 */
1700hterm.Terminal.prototype.insertLines = function(count) {
Robert Ginda579186b2012-09-26 11:40:04 -07001701 var cursorRow = this.screen_.cursorPosition.row;
rginda8ba33642011-12-14 12:31:31 -08001702
1703 var bottom = this.getVTScrollBottom();
Robert Ginda579186b2012-09-26 11:40:04 -07001704 count = Math.min(count, bottom - cursorRow);
rginda8ba33642011-12-14 12:31:31 -08001705
Robert Ginda579186b2012-09-26 11:40:04 -07001706 // The moveCount is the number of rows we need to relocate to make room for
1707 // the new row(s). The count is the distance to move them.
1708 var moveCount = bottom - cursorRow - count + 1;
1709 if (moveCount)
1710 this.moveRows_(cursorRow, moveCount, cursorRow + count);
rginda8ba33642011-12-14 12:31:31 -08001711
Robert Ginda579186b2012-09-26 11:40:04 -07001712 for (var i = count - 1; i >= 0; i--) {
1713 this.setAbsoluteCursorPosition(cursorRow + i, 0);
rginda8ba33642011-12-14 12:31:31 -08001714 this.screen_.clearCursorRow();
1715 }
rginda8ba33642011-12-14 12:31:31 -08001716};
1717
1718/**
1719 * VT command to delete lines at the current cursor row.
1720 *
1721 * New rows are added to the bottom of scroll region to take their place. New
1722 * rows are strictly there to take up space and have no content or style.
1723 */
1724hterm.Terminal.prototype.deleteLines = function(count) {
rginda87b86462011-12-14 13:48:03 -08001725 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001726
rginda87b86462011-12-14 13:48:03 -08001727 var top = cursor.row;
rginda8ba33642011-12-14 12:31:31 -08001728 var bottom = this.getVTScrollBottom();
1729
rginda87b86462011-12-14 13:48:03 -08001730 var maxCount = bottom - top + 1;
rginda8ba33642011-12-14 12:31:31 -08001731 count = Math.min(count, maxCount);
1732
rginda87b86462011-12-14 13:48:03 -08001733 var moveStart = bottom - count + 1;
rginda8ba33642011-12-14 12:31:31 -08001734 if (count != maxCount)
1735 this.moveRows_(top, count, moveStart);
1736
1737 for (var i = 0; i < count; i++) {
rginda87b86462011-12-14 13:48:03 -08001738 this.setAbsoluteCursorPosition(moveStart + i, 0);
rginda8ba33642011-12-14 12:31:31 -08001739 this.screen_.clearCursorRow();
1740 }
1741
rginda87b86462011-12-14 13:48:03 -08001742 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001743 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001744};
1745
1746/**
1747 * Inserts the given number of spaces at the current cursor position.
1748 *
rginda87b86462011-12-14 13:48:03 -08001749 * The cursor position is not changed.
rginda8ba33642011-12-14 12:31:31 -08001750 */
1751hterm.Terminal.prototype.insertSpace = function(count) {
rginda87b86462011-12-14 13:48:03 -08001752 var cursor = this.saveCursor();
1753
rgindacbbd7482012-06-13 15:06:16 -07001754 var ws = lib.f.getWhitespace(count || 1);
rginda8ba33642011-12-14 12:31:31 -08001755 this.screen_.insertString(ws);
rgindaa19afe22012-01-25 15:40:22 -08001756 this.screen_.maybeClipCurrentRow();
rginda87b86462011-12-14 13:48:03 -08001757
1758 this.restoreCursor(cursor);
David Benjamin54e8bf62012-06-01 22:31:40 -04001759 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001760};
1761
1762/**
1763 * Forward-delete the specified number of characters starting at the cursor
1764 * position.
1765 *
1766 * @param {integer} count The number of characters to delete.
1767 */
1768hterm.Terminal.prototype.deleteChars = function(count) {
Robert Ginda7fd57082012-09-25 14:41:47 -07001769 var deleted = this.screen_.deleteChars(count);
1770 if (deleted && !this.screen_.textAttributes.isDefault()) {
1771 var cursor = this.saveCursor();
1772 this.setCursorColumn(this.screenSize.width - deleted);
1773 this.screen_.insertString(lib.f.getWhitespace(deleted));
1774 this.restoreCursor(cursor);
1775 }
1776
David Benjamin54e8bf62012-06-01 22:31:40 -04001777 this.clearCursorOverflow();
rginda8ba33642011-12-14 12:31:31 -08001778};
1779
1780/**
1781 * Shift rows in the scroll region upwards by a given number of lines.
1782 *
1783 * New rows are inserted at the bottom of the scroll region to fill the
1784 * vacated rows. The new rows not filled out with the current text attributes.
1785 *
1786 * This function does not affect the scrollback rows at all. Rows shifted
1787 * off the top are lost.
1788 *
rginda87b86462011-12-14 13:48:03 -08001789 * The cursor position is not altered.
1790 *
rginda8ba33642011-12-14 12:31:31 -08001791 * @param {integer} count The number of rows to scroll.
1792 */
1793hterm.Terminal.prototype.vtScrollUp = function(count) {
rginda87b86462011-12-14 13:48:03 -08001794 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001795
rginda87b86462011-12-14 13:48:03 -08001796 this.setAbsoluteCursorRow(this.getVTScrollTop());
rginda8ba33642011-12-14 12:31:31 -08001797 this.deleteLines(count);
1798
rginda87b86462011-12-14 13:48:03 -08001799 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001800};
1801
1802/**
1803 * Shift rows below the cursor down by a given number of lines.
1804 *
1805 * This function respects the current scroll region.
1806 *
1807 * New rows are inserted at the top of the scroll region to fill the
1808 * vacated rows. The new rows not filled out with the current text attributes.
1809 *
1810 * This function does not affect the scrollback rows at all. Rows shifted
1811 * off the bottom are lost.
1812 *
1813 * @param {integer} count The number of rows to scroll.
1814 */
1815hterm.Terminal.prototype.vtScrollDown = function(opt_count) {
rginda87b86462011-12-14 13:48:03 -08001816 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001817
rginda87b86462011-12-14 13:48:03 -08001818 this.setAbsoluteCursorPosition(this.getVTScrollTop(), 0);
rginda8ba33642011-12-14 12:31:31 -08001819 this.insertLines(opt_count);
1820
rginda87b86462011-12-14 13:48:03 -08001821 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001822};
1823
rginda87b86462011-12-14 13:48:03 -08001824
rginda8ba33642011-12-14 12:31:31 -08001825/**
1826 * Set the cursor position.
1827 *
1828 * The cursor row is relative to the scroll region if the terminal has
1829 * 'origin mode' enabled, or relative to the addressable screen otherwise.
1830 *
1831 * @param {integer} row The new zero-based cursor row.
1832 * @param {integer} row The new zero-based cursor column.
1833 */
1834hterm.Terminal.prototype.setCursorPosition = function(row, column) {
1835 if (this.options_.originMode) {
rginda87b86462011-12-14 13:48:03 -08001836 this.setRelativeCursorPosition(row, column);
rginda8ba33642011-12-14 12:31:31 -08001837 } else {
rginda87b86462011-12-14 13:48:03 -08001838 this.setAbsoluteCursorPosition(row, column);
rginda8ba33642011-12-14 12:31:31 -08001839 }
rginda87b86462011-12-14 13:48:03 -08001840};
rginda8ba33642011-12-14 12:31:31 -08001841
rginda87b86462011-12-14 13:48:03 -08001842hterm.Terminal.prototype.setRelativeCursorPosition = function(row, column) {
1843 var scrollTop = this.getVTScrollTop();
rgindacbbd7482012-06-13 15:06:16 -07001844 row = lib.f.clamp(row + scrollTop, scrollTop, this.getVTScrollBottom());
1845 column = lib.f.clamp(column, 0, this.screenSize.width - 1);
rginda87b86462011-12-14 13:48:03 -08001846 this.screen_.setCursorPosition(row, column);
1847};
1848
1849hterm.Terminal.prototype.setAbsoluteCursorPosition = function(row, column) {
rgindacbbd7482012-06-13 15:06:16 -07001850 row = lib.f.clamp(row, 0, this.screenSize.height - 1);
1851 column = lib.f.clamp(column, 0, this.screenSize.width - 1);
rginda8ba33642011-12-14 12:31:31 -08001852 this.screen_.setCursorPosition(row, column);
1853};
1854
1855/**
1856 * Set the cursor column.
1857 *
1858 * @param {integer} column The new zero-based cursor column.
1859 */
1860hterm.Terminal.prototype.setCursorColumn = function(column) {
rginda87b86462011-12-14 13:48:03 -08001861 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, column);
rginda8ba33642011-12-14 12:31:31 -08001862};
1863
1864/**
1865 * Return the cursor column.
1866 *
1867 * @return {integer} The zero-based cursor column.
1868 */
1869hterm.Terminal.prototype.getCursorColumn = function() {
1870 return this.screen_.cursorPosition.column;
1871};
1872
1873/**
1874 * Set the cursor row.
1875 *
1876 * The cursor row is relative to the scroll region if the terminal has
1877 * 'origin mode' enabled, or relative to the addressable screen otherwise.
1878 *
1879 * @param {integer} row The new cursor row.
1880 */
rginda87b86462011-12-14 13:48:03 -08001881hterm.Terminal.prototype.setAbsoluteCursorRow = function(row) {
1882 this.setAbsoluteCursorPosition(row, this.screen_.cursorPosition.column);
rginda8ba33642011-12-14 12:31:31 -08001883};
1884
1885/**
1886 * Return the cursor row.
1887 *
1888 * @return {integer} The zero-based cursor row.
1889 */
1890hterm.Terminal.prototype.getCursorRow = function(row) {
1891 return this.screen_.cursorPosition.row;
1892};
1893
1894/**
1895 * Request that the ScrollPort redraw itself soon.
1896 *
1897 * The redraw will happen asynchronously, soon after the call stack winds down.
1898 * Multiple calls will be coalesced into a single redraw.
1899 */
1900hterm.Terminal.prototype.scheduleRedraw_ = function() {
rginda87b86462011-12-14 13:48:03 -08001901 if (this.timeouts_.redraw)
1902 return;
rginda8ba33642011-12-14 12:31:31 -08001903
1904 var self = this;
rginda87b86462011-12-14 13:48:03 -08001905 this.timeouts_.redraw = setTimeout(function() {
1906 delete self.timeouts_.redraw;
rginda8ba33642011-12-14 12:31:31 -08001907 self.scrollPort_.redraw_();
1908 }, 0);
1909};
1910
1911/**
1912 * Request that the ScrollPort be scrolled to the bottom.
1913 *
1914 * The scroll will happen asynchronously, soon after the call stack winds down.
1915 * Multiple calls will be coalesced into a single scroll.
1916 *
1917 * This affects the scrollbar position of the ScrollPort, and has nothing to
1918 * do with the VT scroll commands.
1919 */
1920hterm.Terminal.prototype.scheduleScrollDown_ = function() {
1921 if (this.timeouts_.scrollDown)
rginda87b86462011-12-14 13:48:03 -08001922 return;
rginda8ba33642011-12-14 12:31:31 -08001923
1924 var self = this;
1925 this.timeouts_.scrollDown = setTimeout(function() {
1926 delete self.timeouts_.scrollDown;
1927 self.scrollPort_.scrollRowToBottom(self.getRowCount());
1928 }, 10);
1929};
1930
1931/**
1932 * Move the cursor up a specified number of rows.
1933 *
1934 * @param {integer} count The number of rows to move the cursor.
1935 */
1936hterm.Terminal.prototype.cursorUp = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001937 return this.cursorDown(-(count || 1));
rginda8ba33642011-12-14 12:31:31 -08001938};
1939
1940/**
1941 * Move the cursor down a specified number of rows.
1942 *
1943 * @param {integer} count The number of rows to move the cursor.
1944 */
1945hterm.Terminal.prototype.cursorDown = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001946 count = count || 1;
rginda8ba33642011-12-14 12:31:31 -08001947 var minHeight = (this.options_.originMode ? this.getVTScrollTop() : 0);
1948 var maxHeight = (this.options_.originMode ? this.getVTScrollBottom() :
1949 this.screenSize.height - 1);
1950
rgindacbbd7482012-06-13 15:06:16 -07001951 var row = lib.f.clamp(this.screen_.cursorPosition.row + count,
rginda8ba33642011-12-14 12:31:31 -08001952 minHeight, maxHeight);
rginda87b86462011-12-14 13:48:03 -08001953 this.setAbsoluteCursorRow(row);
rginda8ba33642011-12-14 12:31:31 -08001954};
1955
1956/**
1957 * Move the cursor left a specified number of columns.
1958 *
1959 * @param {integer} count The number of columns to move the cursor.
1960 */
1961hterm.Terminal.prototype.cursorLeft = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001962 return this.cursorRight(-(count || 1));
rginda8ba33642011-12-14 12:31:31 -08001963};
1964
1965/**
1966 * Move the cursor right a specified number of columns.
1967 *
1968 * @param {integer} count The number of columns to move the cursor.
1969 */
1970hterm.Terminal.prototype.cursorRight = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001971 count = count || 1;
rgindacbbd7482012-06-13 15:06:16 -07001972 var column = lib.f.clamp(this.screen_.cursorPosition.column + count,
rginda87b86462011-12-14 13:48:03 -08001973 0, this.screenSize.width - 1);
rginda8ba33642011-12-14 12:31:31 -08001974 this.setCursorColumn(column);
1975};
1976
1977/**
1978 * Reverse the foreground and background colors of the terminal.
1979 *
1980 * This only affects text that was drawn with no attributes.
1981 *
1982 * TODO(rginda): Test xterm to see if reverse is respected for text that has
1983 * been drawn with attributes that happen to coincide with the default
1984 * 'no-attribute' colors. My guess is probably not.
1985 */
1986hterm.Terminal.prototype.setReverseVideo = function(state) {
rginda87b86462011-12-14 13:48:03 -08001987 this.options_.reverseVideo = state;
rginda8ba33642011-12-14 12:31:31 -08001988 if (state) {
rginda9f5222b2012-03-05 11:53:28 -08001989 this.scrollPort_.setForegroundColor(this.prefs_.get('background-color'));
1990 this.scrollPort_.setBackgroundColor(this.prefs_.get('foreground-color'));
rginda8ba33642011-12-14 12:31:31 -08001991 } else {
rginda9f5222b2012-03-05 11:53:28 -08001992 this.scrollPort_.setForegroundColor(this.prefs_.get('foreground-color'));
1993 this.scrollPort_.setBackgroundColor(this.prefs_.get('background-color'));
rginda8ba33642011-12-14 12:31:31 -08001994 }
1995};
1996
1997/**
rginda87b86462011-12-14 13:48:03 -08001998 * Ring the terminal bell.
Robert Ginda92e18102013-03-14 13:56:37 -07001999 *
2000 * This will not play the bell audio more than once per second.
rginda87b86462011-12-14 13:48:03 -08002001 */
2002hterm.Terminal.prototype.ringBell = function() {
rginda6d397402012-01-17 10:58:29 -08002003 this.cursorNode_.style.backgroundColor =
2004 this.scrollPort_.getForegroundColor();
rginda87b86462011-12-14 13:48:03 -08002005
2006 var self = this;
2007 setTimeout(function() {
rginda9f5222b2012-03-05 11:53:28 -08002008 self.cursorNode_.style.backgroundColor = self.prefs_.get('cursor-color');
rginda6d397402012-01-17 10:58:29 -08002009 }, 200);
Robert Ginda92e18102013-03-14 13:56:37 -07002010
2011 if (this.bellAudio_.getAttribute('src')) {
Robert Gindaa6331372013-03-19 10:35:39 -07002012 if (this.bellSquelchTimeout_)
Robert Ginda92e18102013-03-14 13:56:37 -07002013 return;
2014
2015 this.bellAudio_.play();
2016
2017 this.bellSequelchTimeout_ = setTimeout(function() {
2018 delete this.bellSquelchTimeout_;
Robert Gindaa6331372013-03-19 10:35:39 -07002019 }.bind(this), 500);
Robert Ginda92e18102013-03-14 13:56:37 -07002020 } else {
2021 delete this.bellSquelchTimeout_;
2022 }
rginda87b86462011-12-14 13:48:03 -08002023};
2024
2025/**
rginda8ba33642011-12-14 12:31:31 -08002026 * Set the origin mode bit.
2027 *
2028 * If origin mode is on, certain VT cursor and scrolling commands measure their
2029 * row parameter relative to the VT scroll region. Otherwise, row 0 corresponds
2030 * to the top of the addressable screen.
2031 *
2032 * Defaults to off.
2033 *
2034 * @param {boolean} state True to set origin mode, false to unset.
2035 */
2036hterm.Terminal.prototype.setOriginMode = function(state) {
2037 this.options_.originMode = state;
rgindae4d29232012-01-19 10:47:13 -08002038 this.setCursorPosition(0, 0);
rginda8ba33642011-12-14 12:31:31 -08002039};
2040
2041/**
2042 * Set the insert mode bit.
2043 *
2044 * If insert mode is on, existing text beyond the cursor position will be
2045 * shifted right to make room for new text. Otherwise, new text overwrites
2046 * any existing text.
2047 *
2048 * Defaults to off.
2049 *
2050 * @param {boolean} state True to set insert mode, false to unset.
2051 */
2052hterm.Terminal.prototype.setInsertMode = function(state) {
2053 this.options_.insertMode = state;
2054};
2055
2056/**
rginda87b86462011-12-14 13:48:03 -08002057 * Set the auto carriage return bit.
2058 *
2059 * If auto carriage return is on then a formfeed character is interpreted
2060 * as a newline, otherwise it's the same as a linefeed. The difference boils
2061 * down to whether or not the cursor column is reset.
2062 */
2063hterm.Terminal.prototype.setAutoCarriageReturn = function(state) {
2064 this.options_.autoCarriageReturn = state;
2065};
2066
2067/**
rginda8ba33642011-12-14 12:31:31 -08002068 * Set the wraparound mode bit.
2069 *
2070 * If wraparound mode is on, certain VT commands will allow the cursor to wrap
2071 * to the start of the following row. Otherwise, the cursor is clamped to the
2072 * end of the screen and attempts to write past it are ignored.
2073 *
2074 * Defaults to on.
2075 *
2076 * @param {boolean} state True to set wraparound mode, false to unset.
2077 */
2078hterm.Terminal.prototype.setWraparound = function(state) {
2079 this.options_.wraparound = state;
2080};
2081
2082/**
2083 * Set the reverse-wraparound mode bit.
2084 *
2085 * If wraparound mode is off, certain VT commands will allow the cursor to wrap
2086 * to the end of the previous row. Otherwise, the cursor is clamped to column
2087 * 0.
2088 *
2089 * Defaults to off.
2090 *
2091 * @param {boolean} state True to set reverse-wraparound mode, false to unset.
2092 */
2093hterm.Terminal.prototype.setReverseWraparound = function(state) {
2094 this.options_.reverseWraparound = state;
2095};
2096
2097/**
2098 * Selects between the primary and alternate screens.
2099 *
2100 * If alternate mode is on, the alternate screen is active. Otherwise the
2101 * primary screen is active.
2102 *
2103 * Swapping screens has no effect on the scrollback buffer.
2104 *
2105 * Each screen maintains its own cursor position.
2106 *
2107 * Defaults to off.
2108 *
2109 * @param {boolean} state True to set alternate mode, false to unset.
2110 */
2111hterm.Terminal.prototype.setAlternateMode = function(state) {
rginda6d397402012-01-17 10:58:29 -08002112 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08002113 this.screen_ = state ? this.alternateScreen_ : this.primaryScreen_;
2114
rginda35c456b2012-02-09 17:29:05 -08002115 if (this.screen_.rowsArray.length &&
2116 this.screen_.rowsArray[0].rowIndex != this.scrollbackRows_.length) {
2117 // If the screen changed sizes while we were away, our rowIndexes may
2118 // be incorrect.
2119 var offset = this.scrollbackRows_.length;
2120 var ary = this.screen_.rowsArray;
rgindacbbd7482012-06-13 15:06:16 -07002121 for (var i = 0; i < ary.length; i++) {
rginda35c456b2012-02-09 17:29:05 -08002122 ary[i].rowIndex = offset + i;
2123 }
2124 }
rginda8ba33642011-12-14 12:31:31 -08002125
rginda35c456b2012-02-09 17:29:05 -08002126 this.realizeWidth_(this.screenSize.width);
2127 this.realizeHeight_(this.screenSize.height);
2128 this.scrollPort_.syncScrollHeight();
2129 this.scrollPort_.invalidate();
rginda8ba33642011-12-14 12:31:31 -08002130
rginda6d397402012-01-17 10:58:29 -08002131 this.restoreCursor(cursor);
rginda35c456b2012-02-09 17:29:05 -08002132 this.scrollPort_.resize();
rginda8ba33642011-12-14 12:31:31 -08002133};
2134
2135/**
2136 * Set the cursor-blink mode bit.
2137 *
2138 * If cursor-blink is on, the cursor will blink when it is visible. Otherwise
2139 * a visible cursor does not blink.
2140 *
2141 * You should make sure to turn blinking off if you're going to dispose of a
2142 * terminal, otherwise you'll leak a timeout.
2143 *
2144 * Defaults to on.
2145 *
2146 * @param {boolean} state True to set cursor-blink mode, false to unset.
2147 */
2148hterm.Terminal.prototype.setCursorBlink = function(state) {
2149 this.options_.cursorBlink = state;
2150
2151 if (!state && this.timeouts_.cursorBlink) {
2152 clearTimeout(this.timeouts_.cursorBlink);
2153 delete this.timeouts_.cursorBlink;
2154 }
2155
2156 if (this.options_.cursorVisible)
2157 this.setCursorVisible(true);
2158};
2159
2160/**
2161 * Set the cursor-visible mode bit.
2162 *
2163 * If cursor-visible is on, the cursor will be visible. Otherwise it will not.
2164 *
2165 * Defaults to on.
2166 *
2167 * @param {boolean} state True to set cursor-visible mode, false to unset.
2168 */
2169hterm.Terminal.prototype.setCursorVisible = function(state) {
2170 this.options_.cursorVisible = state;
2171
2172 if (!state) {
rginda87b86462011-12-14 13:48:03 -08002173 this.cursorNode_.style.opacity = '0';
rginda8ba33642011-12-14 12:31:31 -08002174 return;
2175 }
2176
rginda87b86462011-12-14 13:48:03 -08002177 this.syncCursorPosition_();
2178
2179 this.cursorNode_.style.opacity = '1';
rginda8ba33642011-12-14 12:31:31 -08002180
2181 if (this.options_.cursorBlink) {
2182 if (this.timeouts_.cursorBlink)
2183 return;
2184
2185 this.timeouts_.cursorBlink = setInterval(this.onCursorBlink_.bind(this),
2186 500);
2187 } else {
2188 if (this.timeouts_.cursorBlink) {
2189 clearTimeout(this.timeouts_.cursorBlink);
2190 delete this.timeouts_.cursorBlink;
2191 }
2192 }
2193};
2194
2195/**
rginda87b86462011-12-14 13:48:03 -08002196 * Synchronizes the visible cursor and document selection with the current
2197 * cursor coordinates.
rginda8ba33642011-12-14 12:31:31 -08002198 */
2199hterm.Terminal.prototype.syncCursorPosition_ = function() {
2200 var topRowIndex = this.scrollPort_.getTopRowIndex();
2201 var bottomRowIndex = this.scrollPort_.getBottomRowIndex(topRowIndex);
2202 var cursorRowIndex = this.scrollbackRows_.length +
2203 this.screen_.cursorPosition.row;
2204
2205 if (cursorRowIndex > bottomRowIndex) {
2206 // Cursor is scrolled off screen, move it outside of the visible area.
rginda35c456b2012-02-09 17:29:05 -08002207 this.cursorNode_.style.top = -this.scrollPort_.characterSize.height + 'px';
rginda8ba33642011-12-14 12:31:31 -08002208 return;
2209 }
2210
2211 this.cursorNode_.style.top = this.scrollPort_.visibleRowTopMargin +
rginda35c456b2012-02-09 17:29:05 -08002212 this.scrollPort_.characterSize.height * (cursorRowIndex - topRowIndex) +
2213 'px';
2214 this.cursorNode_.style.left = this.scrollPort_.characterSize.width *
2215 this.screen_.cursorPosition.column + 'px';
rginda87b86462011-12-14 13:48:03 -08002216
2217 this.cursorNode_.setAttribute('title',
2218 '(' + this.screen_.cursorPosition.row +
2219 ', ' + this.screen_.cursorPosition.column +
2220 ')');
2221
2222 // Update the caret for a11y purposes.
2223 var selection = this.document_.getSelection();
2224 if (selection && selection.isCollapsed)
2225 this.screen_.syncSelectionCaret(selection);
rginda8ba33642011-12-14 12:31:31 -08002226};
2227
Robert Gindafb1be6a2013-12-11 11:56:22 -08002228/**
2229 * Adjusts the style of this.cursorNode_ according to the current cursor shape
2230 * and character cell dimensions.
2231 */
Robert Ginda830583c2013-08-07 13:20:46 -07002232hterm.Terminal.prototype.restyleCursor_ = function() {
2233 var shape = this.cursorShape_;
2234
2235 if (this.cursorNode_.getAttribute('focus') == 'false') {
2236 // Always show a block cursor when unfocused.
2237 shape = hterm.Terminal.cursorShape.BLOCK;
2238 }
2239
2240 var style = this.cursorNode_.style;
2241
Robert Gindafb1be6a2013-12-11 11:56:22 -08002242 style.width = this.scrollPort_.characterSize.width + 'px';
2243
Robert Ginda830583c2013-08-07 13:20:46 -07002244 switch (shape) {
2245 case hterm.Terminal.cursorShape.BEAM:
2246 style.height = this.scrollPort_.characterSize.height + 'px';
2247 style.backgroundColor = 'transparent';
2248 style.borderBottomStyle = null;
2249 style.borderLeftStyle = 'solid';
2250 break;
2251
2252 case hterm.Terminal.cursorShape.UNDERLINE:
2253 style.height = this.scrollPort_.characterSize.baseline + 'px';
2254 style.backgroundColor = 'transparent';
2255 style.borderBottomStyle = 'solid';
2256 // correct the size to put it exactly at the baseline
2257 style.borderLeftStyle = null;
2258 break;
2259
2260 default:
2261 style.height = this.scrollPort_.characterSize.height + 'px';
2262 style.backgroundColor = this.cursorColor_;
2263 style.borderBottomStyle = null;
2264 style.borderLeftStyle = null;
2265 break;
2266 }
2267};
2268
rginda8ba33642011-12-14 12:31:31 -08002269/**
2270 * Synchronizes the visible cursor with the current cursor coordinates.
2271 *
2272 * The sync will happen asynchronously, soon after the call stack winds down.
2273 * Multiple calls will be coalesced into a single sync.
2274 */
2275hterm.Terminal.prototype.scheduleSyncCursorPosition_ = function() {
2276 if (this.timeouts_.syncCursor)
rginda87b86462011-12-14 13:48:03 -08002277 return;
rginda8ba33642011-12-14 12:31:31 -08002278
2279 var self = this;
2280 this.timeouts_.syncCursor = setTimeout(function() {
2281 self.syncCursorPosition_();
2282 delete self.timeouts_.syncCursor;
rginda87b86462011-12-14 13:48:03 -08002283 }, 0);
2284};
2285
rgindacc2996c2012-02-24 14:59:31 -08002286/**
rgindaf522ce02012-04-17 17:49:17 -07002287 * Show or hide the zoom warning.
2288 *
2289 * The zoom warning is a message warning the user that their browser zoom must
2290 * be set to 100% in order for hterm to function properly.
2291 *
2292 * @param {boolean} state True to show the message, false to hide it.
2293 */
2294hterm.Terminal.prototype.showZoomWarning_ = function(state) {
2295 if (!this.zoomWarningNode_) {
2296 if (!state)
2297 return;
2298
2299 this.zoomWarningNode_ = this.document_.createElement('div');
2300 this.zoomWarningNode_.style.cssText = (
2301 'color: black;' +
2302 'background-color: #ff2222;' +
2303 'font-size: large;' +
2304 'border-radius: 8px;' +
2305 'opacity: 0.75;' +
2306 'padding: 0.2em 0.5em 0.2em 0.5em;' +
2307 'top: 0.5em;' +
2308 'right: 1.2em;' +
2309 'position: absolute;' +
2310 '-webkit-text-size-adjust: none;' +
2311 '-webkit-user-select: none;');
rgindaf522ce02012-04-17 17:49:17 -07002312 }
2313
Robert Gindab4839c22013-02-28 16:52:10 -08002314 this.zoomWarningNode_.textContent = lib.MessageManager.replaceReferences(
2315 hterm.zoomWarningMessage,
2316 [parseInt(this.scrollPort_.characterSize.zoomFactor * 100)]);
2317
rgindaf522ce02012-04-17 17:49:17 -07002318 this.zoomWarningNode_.style.fontFamily = this.prefs_.get('font-family');
2319
2320 if (state) {
2321 if (!this.zoomWarningNode_.parentNode)
2322 this.div_.parentNode.appendChild(this.zoomWarningNode_);
2323 } else if (this.zoomWarningNode_.parentNode) {
2324 this.zoomWarningNode_.parentNode.removeChild(this.zoomWarningNode_);
2325 }
2326};
2327
2328/**
rgindacc2996c2012-02-24 14:59:31 -08002329 * Show the terminal overlay for a given amount of time.
2330 *
2331 * The terminal overlay appears in inverse video in a large font, centered
2332 * over the terminal. You should probably keep the overlay message brief,
2333 * since it's in a large font and you probably aren't going to check the size
2334 * of the terminal first.
2335 *
2336 * @param {string} msg The text (not HTML) message to display in the overlay.
2337 * @param {number} opt_timeout The amount of time to wait before fading out
2338 * the overlay. Defaults to 1.5 seconds. Pass null to have the overlay
2339 * stay up forever (or until the next overlay).
2340 */
2341hterm.Terminal.prototype.showOverlay = function(msg, opt_timeout) {
rgindaf0090c92012-02-10 14:58:52 -08002342 if (!this.overlayNode_) {
2343 if (!this.div_)
2344 return;
2345
2346 this.overlayNode_ = this.document_.createElement('div');
2347 this.overlayNode_.style.cssText = (
rgindaf0090c92012-02-10 14:58:52 -08002348 'border-radius: 15px;' +
rgindaf0090c92012-02-10 14:58:52 -08002349 'font-size: xx-large;' +
2350 'opacity: 0.75;' +
2351 'padding: 0.2em 0.5em 0.2em 0.5em;' +
2352 'position: absolute;' +
2353 '-webkit-user-select: none;' +
2354 '-webkit-transition: opacity 180ms ease-in;');
Robert Ginda70926e42013-11-25 14:56:36 -08002355
2356 this.overlayNode_.addEventListener('mousedown', function(e) {
2357 e.preventDefault();
2358 e.stopPropagation();
2359 }, true);
rgindaf0090c92012-02-10 14:58:52 -08002360 }
2361
rginda9f5222b2012-03-05 11:53:28 -08002362 this.overlayNode_.style.color = this.prefs_.get('background-color');
2363 this.overlayNode_.style.backgroundColor = this.prefs_.get('foreground-color');
2364 this.overlayNode_.style.fontFamily = this.prefs_.get('font-family');
2365
rgindaf0090c92012-02-10 14:58:52 -08002366 this.overlayNode_.textContent = msg;
2367 this.overlayNode_.style.opacity = '0.75';
2368
2369 if (!this.overlayNode_.parentNode)
2370 this.div_.appendChild(this.overlayNode_);
2371
Robert Ginda97769282013-02-01 15:30:30 -08002372 var divSize = hterm.getClientSize(this.div_);
2373 var overlaySize = hterm.getClientSize(this.overlayNode_);
2374
2375 this.overlayNode_.style.top = (divSize.height - overlaySize.height) / 2;
2376 this.overlayNode_.style.left = (divSize.width - overlaySize.width -
2377 this.scrollPort_.currentScrollbarWidthPx) / 2;
rgindaf0090c92012-02-10 14:58:52 -08002378
2379 var self = this;
2380
2381 if (this.overlayTimeout_)
2382 clearTimeout(this.overlayTimeout_);
2383
rgindacc2996c2012-02-24 14:59:31 -08002384 if (opt_timeout === null)
2385 return;
2386
rgindaf0090c92012-02-10 14:58:52 -08002387 this.overlayTimeout_ = setTimeout(function() {
2388 self.overlayNode_.style.opacity = '0';
Robert Ginda70926e42013-11-25 14:56:36 -08002389 self.overlayTimeout_ = setTimeout(function() {
rginda259dcca2012-03-14 16:37:11 -07002390 if (self.overlayNode_.parentNode)
2391 self.overlayNode_.parentNode.removeChild(self.overlayNode_);
rgindaf0090c92012-02-10 14:58:52 -08002392 self.overlayTimeout_ = null;
2393 self.overlayNode_.style.opacity = '0.75';
2394 }, 200);
rgindacc2996c2012-02-24 14:59:31 -08002395 }, opt_timeout || 1500);
rgindaf0090c92012-02-10 14:58:52 -08002396};
2397
rginda4bba5e12012-06-20 16:15:30 -07002398/**
2399 * Paste from the system clipboard to the terminal.
2400 */
2401hterm.Terminal.prototype.paste = function() {
2402 hterm.pasteFromClipboard(this.document_);
2403};
2404
2405/**
2406 * Copy a string to the system clipboard.
2407 *
2408 * Note: If there is a selected range in the terminal, it'll be cleared.
2409 */
2410hterm.Terminal.prototype.copyStringToClipboard = function(str) {
Robert Ginda9fb38222012-09-11 14:19:12 -07002411 if (this.prefs_.get('enable-clipboard-notice'))
Robert Gindab4839c22013-02-28 16:52:10 -08002412 setTimeout(this.showOverlay.bind(this, hterm.notifyCopyMessage, 500), 200);
rgindaa09e7332012-08-17 12:49:51 -07002413
2414 var copySource = this.document_.createElement('pre');
rginda4bba5e12012-06-20 16:15:30 -07002415 copySource.textContent = str;
2416 copySource.style.cssText = (
2417 '-webkit-user-select: text;' +
2418 'position: absolute;' +
2419 'top: -99px');
2420
2421 this.document_.body.appendChild(copySource);
rgindafaa74742012-08-21 13:34:03 -07002422
rginda4bba5e12012-06-20 16:15:30 -07002423 var selection = this.document_.getSelection();
rgindafaa74742012-08-21 13:34:03 -07002424 var anchorNode = selection.anchorNode;
2425 var anchorOffset = selection.anchorOffset;
2426 var focusNode = selection.focusNode;
2427 var focusOffset = selection.focusOffset;
2428
rginda4bba5e12012-06-20 16:15:30 -07002429 selection.selectAllChildren(copySource);
2430
rgindaa09e7332012-08-17 12:49:51 -07002431 hterm.copySelectionToClipboard(this.document_);
rginda4bba5e12012-06-20 16:15:30 -07002432
rgindafaa74742012-08-21 13:34:03 -07002433 selection.collapse(anchorNode, anchorOffset);
2434 selection.extend(focusNode, focusOffset);
2435
rginda4bba5e12012-06-20 16:15:30 -07002436 copySource.parentNode.removeChild(copySource);
2437};
2438
rgindaa09e7332012-08-17 12:49:51 -07002439hterm.Terminal.prototype.getSelectionText = function() {
2440 var selection = this.scrollPort_.selection;
2441 selection.sync();
2442
2443 if (selection.isCollapsed)
2444 return null;
2445
2446
2447 // Start offset measures from the beginning of the line.
2448 var startOffset = selection.startOffset;
2449 var node = selection.startNode;
Robert Ginda0d190502012-10-02 10:59:00 -07002450
Robert Gindafdbb3f22012-09-06 20:23:06 -07002451 if (node.nodeName != 'X-ROW') {
2452 // If the selection doesn't start on an x-row node, then it must be
2453 // somewhere inside the x-row. Add any characters from previous siblings
2454 // into the start offset.
Robert Ginda0d190502012-10-02 10:59:00 -07002455
2456 if (node.nodeName == '#text' && node.parentNode.nodeName == 'SPAN') {
2457 // If node is the text node in a styled span, move up to the span node.
2458 node = node.parentNode;
2459 }
2460
Robert Gindafdbb3f22012-09-06 20:23:06 -07002461 while (node.previousSibling) {
2462 node = node.previousSibling;
Ricky Liang48f05cb2013-12-31 23:35:29 +08002463 startOffset += hterm.TextAttributes.nodeWidth(node);
Robert Gindafdbb3f22012-09-06 20:23:06 -07002464 }
rgindaa09e7332012-08-17 12:49:51 -07002465 }
2466
2467 // End offset measures from the end of the line.
Ricky Liang48f05cb2013-12-31 23:35:29 +08002468 var endOffset = (hterm.TextAttributes.nodeWidth(selection.endNode) -
2469 selection.endOffset);
rgindaa09e7332012-08-17 12:49:51 -07002470 var node = selection.endNode;
Robert Ginda0d190502012-10-02 10:59:00 -07002471
Robert Gindafdbb3f22012-09-06 20:23:06 -07002472 if (node.nodeName != 'X-ROW') {
2473 // If the selection doesn't end on an x-row node, then it must be
2474 // somewhere inside the x-row. Add any characters from following siblings
2475 // into the end offset.
Robert Ginda0d190502012-10-02 10:59:00 -07002476
2477 if (node.nodeName == '#text' && node.parentNode.nodeName == 'SPAN') {
2478 // If node is the text node in a styled span, move up to the span node.
2479 node = node.parentNode;
2480 }
2481
Robert Gindafdbb3f22012-09-06 20:23:06 -07002482 while (node.nextSibling) {
2483 node = node.nextSibling;
Ricky Liang48f05cb2013-12-31 23:35:29 +08002484 endOffset += hterm.TextAttributes.nodeWidth(node);
Robert Gindafdbb3f22012-09-06 20:23:06 -07002485 }
rgindaa09e7332012-08-17 12:49:51 -07002486 }
2487
2488 var rv = this.getRowsText(selection.startRow.rowIndex,
2489 selection.endRow.rowIndex + 1);
Ricky Liang48f05cb2013-12-31 23:35:29 +08002490 return lib.wc.substring(rv, startOffset, lib.wc.strWidth(rv) - endOffset);
rgindaa09e7332012-08-17 12:49:51 -07002491};
2492
rginda4bba5e12012-06-20 16:15:30 -07002493/**
2494 * Copy the current selection to the system clipboard, then clear it after a
2495 * short delay.
2496 */
2497hterm.Terminal.prototype.copySelectionToClipboard = function() {
rgindaa09e7332012-08-17 12:49:51 -07002498 var text = this.getSelectionText();
2499 if (text != null)
2500 this.copyStringToClipboard(text);
rginda4bba5e12012-06-20 16:15:30 -07002501};
2502
rgindaf0090c92012-02-10 14:58:52 -08002503hterm.Terminal.prototype.overlaySize = function() {
2504 this.showOverlay(this.screenSize.width + 'x' + this.screenSize.height);
2505};
2506
rginda87b86462011-12-14 13:48:03 -08002507/**
2508 * Invoked by hterm.Terminal.Keyboard when a VT keystroke is detected.
2509 *
Robert Ginda8cb7d902013-06-20 14:37:18 -07002510 * @param {string} string The VT string representing the keystroke, in UTF-16.
rginda87b86462011-12-14 13:48:03 -08002511 */
2512hterm.Terminal.prototype.onVTKeystroke = function(string) {
rginda9f5222b2012-03-05 11:53:28 -08002513 if (this.scrollOnKeystroke_)
rginda87b86462011-12-14 13:48:03 -08002514 this.scrollPort_.scrollRowToBottom(this.getRowCount());
2515
Robert Ginda8cb7d902013-06-20 14:37:18 -07002516 this.io.onVTKeystroke(this.keyboard.encode(string));
rginda8ba33642011-12-14 12:31:31 -08002517};
2518
2519/**
rgindad5613292012-06-19 15:40:37 -07002520 * Add the terminalRow and terminalColumn properties to mouse events and
2521 * then forward on to onMouse().
2522 *
2523 * The terminalRow and terminalColumn properties contain the (row, column)
2524 * coordinates for the mouse event.
2525 */
2526hterm.Terminal.prototype.onMouse_ = function(e) {
rgindafaa74742012-08-21 13:34:03 -07002527 if (e.processedByTerminalHandler_) {
2528 // We register our event handlers on the document, as well as the cursor
2529 // and the scroll blocker. Mouse events that occur on the cursor or
2530 // scroll blocker will also appear on the document, but we don't want to
2531 // process them twice.
2532 //
2533 // We can't just prevent bubbling because that has other side effects, so
2534 // we decorate the event object with this property instead.
2535 return;
2536 }
2537
2538 e.processedByTerminalHandler_ = true;
2539
Robert Ginda4e24f8f2014-01-08 14:45:06 -08002540 if (e.type == 'mousedown' && e.terminalColumn > this.screenSize.width) {
2541 // Mousedown in the scrollbar area.
rginda4bba5e12012-06-20 16:15:30 -07002542 return;
2543 }
2544
rgindad5613292012-06-19 15:40:37 -07002545 e.terminalRow = parseInt((e.clientY - this.scrollPort_.visibleRowTopMargin) /
2546 this.scrollPort_.characterSize.height) + 1;
2547 e.terminalColumn = parseInt(e.clientX /
2548 this.scrollPort_.characterSize.width) + 1;
2549
Robert Ginda4e24f8f2014-01-08 14:45:06 -08002550 if (this.enableMouseDragScroll) {
2551 if (e.type == 'dblclick') {
2552 this.screen_.expandSelection(this.document_.getSelection());
2553 hterm.copySelectionToClipboard(this.document_);
rgindad5613292012-06-19 15:40:37 -07002554 return;
2555 }
2556
Robert Ginda4e24f8f2014-01-08 14:45:06 -08002557 if (e.type == 'mousedown' && e.which == this.mousePasteButton) {
2558 this.paste();
2559 return;
rgindad5613292012-06-19 15:40:37 -07002560 }
Robert Ginda4e24f8f2014-01-08 14:45:06 -08002561
2562 if (e.type == 'mouseup' && e.which == 1 && this.copyOnSelect &&
2563 !this.document_.getSelection().isCollapsed) {
2564 hterm.copySelectionToClipboard(this.document_);
2565 return;
2566 }
2567
2568 if ((e.type == 'mousemove' || e.type == 'mouseup') &&
2569 this.scrollBlockerNode_.engaged) {
2570 // Disengage the scroll-blocker after one of these events.
2571 this.scrollBlockerNode_.engaged = false;
2572 this.scrollBlockerNode_.style.top = '-99px';
2573 }
2574
2575 } else /* if (!this.enableMouseDragScroll) */ {
2576 if (!this.scrollBlockerNode_.engaged) {
2577 if (e.type == 'mousedown') {
2578 // Move the scroll-blocker into place if we want to keep the scrollport
2579 // from scrolling.
2580 this.scrollBlockerNode_.engaged = true;
2581 this.scrollBlockerNode_.style.top = (e.clientY - 5) + 'px';
2582 this.scrollBlockerNode_.style.left = (e.clientX - 5) + 'px';
2583 } else if (e.type == 'mousemove') {
2584 // Oh. This means that drag-scroll was disabled AFTER the mouse down,
2585 // in which case it's too late to engage the scroll-blocker.
2586 this.document_.getSelection().collapse();
2587 e.preventDefault();
2588 }
2589 }
rgindad5613292012-06-19 15:40:37 -07002590 }
2591
rgindafaa74742012-08-21 13:34:03 -07002592 this.onMouse(e);
rgindad5613292012-06-19 15:40:37 -07002593};
2594
2595/**
2596 * Clients should override this if they care to know about mouse events.
2597 *
2598 * The event parameter will be a normal DOM mouse click event with additional
2599 * 'terminalRow' and 'terminalColumn' properties.
2600 */
2601hterm.Terminal.prototype.onMouse = function(e) { };
2602
2603/**
rginda8e92a692012-05-20 19:37:20 -07002604 * React when focus changes.
2605 */
2606hterm.Terminal.prototype.onFocusChange_ = function(state) {
2607 this.cursorNode_.setAttribute('focus', state ? 'true' : 'false');
Robert Ginda830583c2013-08-07 13:20:46 -07002608 this.restyleCursor_();
rginda8e92a692012-05-20 19:37:20 -07002609};
2610
2611/**
rginda8ba33642011-12-14 12:31:31 -08002612 * React when the ScrollPort is scrolled.
2613 */
2614hterm.Terminal.prototype.onScroll_ = function() {
2615 this.scheduleSyncCursorPosition_();
2616};
2617
2618/**
rginda9846e2f2012-01-27 13:53:33 -08002619 * React when text is pasted into the scrollPort.
2620 */
2621hterm.Terminal.prototype.onPaste_ = function(e) {
Robert Ginda8cb7d902013-06-20 14:37:18 -07002622 this.onVTKeystroke(e.text.replace(/\n/mg, '\r'));
rginda9846e2f2012-01-27 13:53:33 -08002623};
2624
2625/**
rgindaa09e7332012-08-17 12:49:51 -07002626 * React when the user tries to copy from the scrollPort.
2627 */
2628hterm.Terminal.prototype.onCopy_ = function(e) {
2629 e.preventDefault();
rgindafaa74742012-08-21 13:34:03 -07002630 this.copySelectionToClipboard();
rgindaa09e7332012-08-17 12:49:51 -07002631};
2632
2633/**
rginda8ba33642011-12-14 12:31:31 -08002634 * React when the ScrollPort is resized.
rgindac9bc5502012-01-18 11:48:44 -08002635 *
2636 * Note: This function should not directly contain code that alters the internal
2637 * state of the terminal. That kind of code belongs in realizeWidth or
2638 * realizeHeight, so that it can be executed synchronously in the case of a
2639 * programmatic width change.
rginda8ba33642011-12-14 12:31:31 -08002640 */
2641hterm.Terminal.prototype.onResize_ = function() {
rgindac9bc5502012-01-18 11:48:44 -08002642 var columnCount = Math.floor(this.scrollPort_.getScreenWidth() /
rginda35c456b2012-02-09 17:29:05 -08002643 this.scrollPort_.characterSize.width);
2644 var rowCount = Math.floor(this.scrollPort_.getScreenHeight() /
2645 this.scrollPort_.characterSize.height);
2646
Robert Ginda4e83f3a2012-09-04 15:25:25 -07002647 if (columnCount <= 0 || rowCount <= 0) {
rginda35c456b2012-02-09 17:29:05 -08002648 // We avoid these situations since they happen sometimes when the terminal
Robert Ginda4e83f3a2012-09-04 15:25:25 -07002649 // gets removed from the document or during the initial load, and we can't
2650 // deal with that.
rginda35c456b2012-02-09 17:29:05 -08002651 return;
2652 }
2653
rgindaa8ba17d2012-08-15 14:41:10 -07002654 var isNewSize = (columnCount != this.screenSize.width ||
2655 rowCount != this.screenSize.height);
2656
2657 // We do this even if the size didn't change, just to be sure everything is
2658 // in sync.
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +04002659 this.realizeSize_(columnCount, rowCount);
rgindaf522ce02012-04-17 17:49:17 -07002660 this.showZoomWarning_(this.scrollPort_.characterSize.zoomFactor != 1);
rgindaa8ba17d2012-08-15 14:41:10 -07002661
2662 if (isNewSize)
2663 this.overlaySize();
2664
Robert Gindafb1be6a2013-12-11 11:56:22 -08002665 this.restyleCursor_();
rgindaa8ba17d2012-08-15 14:41:10 -07002666 this.scheduleSyncCursorPosition_();
rginda8ba33642011-12-14 12:31:31 -08002667};
2668
2669/**
2670 * Service the cursor blink timeout.
2671 */
2672hterm.Terminal.prototype.onCursorBlink_ = function() {
Robert Ginda830583c2013-08-07 13:20:46 -07002673 if (this.cursorNode_.getAttribute('focus') == 'false' ||
2674 this.cursorNode_.style.opacity == '0') {
rginda87b86462011-12-14 13:48:03 -08002675 this.cursorNode_.style.opacity = '1';
rginda8ba33642011-12-14 12:31:31 -08002676 } else {
rginda87b86462011-12-14 13:48:03 -08002677 this.cursorNode_.style.opacity = '0';
rginda8ba33642011-12-14 12:31:31 -08002678 }
2679};
David Reveman8f552492012-03-28 12:18:41 -04002680
2681/**
2682 * Set the scrollbar-visible mode bit.
2683 *
2684 * If scrollbar-visible is on, the vertical scrollbar will be visible.
2685 * Otherwise it will not.
2686 *
2687 * Defaults to on.
2688 *
2689 * @param {boolean} state True to set scrollbar-visible mode, false to unset.
2690 */
2691hterm.Terminal.prototype.setScrollbarVisible = function(state) {
2692 this.scrollPort_.setScrollbarVisible(state);
2693};