blob: c0e00d84d4c6294533bcf7b100145039f8cf7458 [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
5/**
6 * Constructor for the Terminal class.
7 *
8 * A Terminal pulls together the hterm.ScrollPort, hterm.Screen and hterm.VT100
9 * classes to provide the complete terminal functionality.
10 *
11 * There are a number of lower-level Terminal methods that can be called
12 * directly to manipulate the cursor, text, scroll region, and other terminal
13 * attributes. However, the primary method is interpret(), which parses VT
14 * escape sequences and invokes the appropriate Terminal methods.
15 *
16 * This class was heavily influenced by Cory Maccarrone's Framebuffer class.
17 *
18 * TODO(rginda): Eventually we're going to need to support characters which are
19 * displayed twice as wide as standard latin characters. This is to support
20 * CJK (and possibly other character sets).
rginda9f5222b2012-03-05 11:53:28 -080021 *
22 * @param {string} opt_profileName Optional preference profile name. If not
23 * provided, defaults to 'default'.
rginda8ba33642011-12-14 12:31:31 -080024 */
rginda9f5222b2012-03-05 11:53:28 -080025hterm.Terminal = function(opt_profileName) {
26 this.profileName_ = null;
27 this.setProfile(opt_profileName || 'default');
28
rginda8ba33642011-12-14 12:31:31 -080029 // Two screen instances.
30 this.primaryScreen_ = new hterm.Screen();
31 this.alternateScreen_ = new hterm.Screen();
32
33 // The "current" screen.
34 this.screen_ = this.primaryScreen_;
35
rginda8ba33642011-12-14 12:31:31 -080036 // The local notion of the screen size. ScreenBuffers also have a size which
37 // indicates their present size. During size changes, the two may disagree.
38 // Also, the inactive screen's size is not altered until it is made the active
39 // screen.
40 this.screenSize = new hterm.Size(0, 0);
41
rginda8ba33642011-12-14 12:31:31 -080042 // The scroll port we'll be using to display the visible rows.
rginda35c456b2012-02-09 17:29:05 -080043 this.scrollPort_ = new hterm.ScrollPort(this);
rginda8ba33642011-12-14 12:31:31 -080044 this.scrollPort_.subscribe('resize', this.onResize_.bind(this));
45 this.scrollPort_.subscribe('scroll', this.onScroll_.bind(this));
rginda9846e2f2012-01-27 13:53:33 -080046 this.scrollPort_.subscribe('paste', this.onPaste_.bind(this));
rginda8ba33642011-12-14 12:31:31 -080047
rginda87b86462011-12-14 13:48:03 -080048 // The div that contains this terminal.
49 this.div_ = null;
50
rgindac9bc5502012-01-18 11:48:44 -080051 // The document that contains the scrollPort. Defaulted to the global
52 // document here so that the terminal is functional even if it hasn't been
53 // inserted into a document yet, but re-set in decorate().
54 this.document_ = window.document;
rginda87b86462011-12-14 13:48:03 -080055
rginda8ba33642011-12-14 12:31:31 -080056 // The rows that have scrolled off screen and are no longer addressable.
57 this.scrollbackRows_ = [];
58
rgindac9bc5502012-01-18 11:48:44 -080059 // Saved tab stops.
60 this.tabStops_ = [];
61
rginda8ba33642011-12-14 12:31:31 -080062 // The VT's notion of the top and bottom rows. Used during some VT
63 // cursor positioning and scrolling commands.
64 this.vtScrollTop_ = null;
65 this.vtScrollBottom_ = null;
66
67 // The DIV element for the visible cursor.
68 this.cursorNode_ = null;
69
rginda9f5222b2012-03-05 11:53:28 -080070 // These prefs are cached so we don't have to read from local storage with
71 // each output and keystroke.
72 this.scrollOnOutput_ = this.prefs_.get('scroll-on-output');
73 this.scrollOnKeystroke_ = this.prefs_.get('scroll-on-keystroke');
74
rgindaf0090c92012-02-10 14:58:52 -080075 // Terminal bell sound.
76 this.bellAudio_ = this.document_.createElement('audio');
rginda9f5222b2012-03-05 11:53:28 -080077 this.bellAudio_.setAttribute('src', this.prefs_.get('audible-bell-sound'));
rgindaf0090c92012-02-10 14:58:52 -080078 this.bellAudio_.setAttribute('preload', 'auto');
79
rginda6d397402012-01-17 10:58:29 -080080 // Cursor position and attributes saved with DECSC.
81 this.savedOptions_ = {};
82
rginda8ba33642011-12-14 12:31:31 -080083 // The current mode bits for the terminal.
84 this.options_ = new hterm.Options();
85
86 // Timeouts we might need to clear.
87 this.timeouts_ = {};
rginda87b86462011-12-14 13:48:03 -080088
89 // The VT escape sequence interpreter.
rginda0f5c0292012-01-13 11:00:13 -080090 this.vt = new hterm.VT(this);
rginda87b86462011-12-14 13:48:03 -080091
rgindafeaf3142012-01-31 15:14:20 -080092 // The keyboard hander.
93 this.keyboard = new hterm.Keyboard(this);
94
rginda87b86462011-12-14 13:48:03 -080095 // General IO interface that can be given to third parties without exposing
96 // the entire terminal object.
97 this.io = new hterm.Terminal.IO(this);
rgindac9bc5502012-01-18 11:48:44 -080098
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +040099 this.realizeSize_(80, 24);
rgindac9bc5502012-01-18 11:48:44 -0800100 this.setDefaultTabStops();
rginda87b86462011-12-14 13:48:03 -0800101};
102
103/**
rginda35c456b2012-02-09 17:29:05 -0800104 * Default tab with of 8 to match xterm.
105 */
106hterm.Terminal.prototype.tabWidth = 8;
107
108/**
rginda35c456b2012-02-09 17:29:05 -0800109 * The assumed width of a scrollbar.
110 */
111hterm.Terminal.prototype.scrollbarWidthPx = 16;
112
113/**
rginda9f5222b2012-03-05 11:53:28 -0800114 * Select a preference profile.
115 *
116 * This will load the terminal preferences for the given profile name and
117 * associate subsequent preference changes with the new preference profile.
118 *
119 * @param {string} newName The name of the preference profile. Forward slash
120 * characters will be removed from the name.
121 */
122hterm.Terminal.prototype.setProfile = function(profileName) {
123 // If we already have a profile selected, we're going to need to re-sync
124 // with the new profile.
125 var needSync = !!this.profileName_;
126
127 this.profileName_ = profileName.replace(/\//g, '');
128
129 this.prefs_ = new hterm.PreferenceManager(
130 '/hterm/prefs/profiles/' + this.profileName_);
131
132 var self = this;
133 this.prefs_.definePreferences
rginda30f20f62012-04-05 16:36:19 -0700134 ([
135 /**
136 * Set whether the alt key acts as a meta key or as a distinct alt key.
rginda9f5222b2012-03-05 11:53:28 -0800137 */
rginda30f20f62012-04-05 16:36:19 -0700138 ['alt-is-meta', false, function(v) {
139 self.vt.keyboard.altIsMeta = v;
rginda9f5222b2012-03-05 11:53:28 -0800140 }
141 ],
142
rginda30f20f62012-04-05 16:36:19 -0700143 /**
rginda39bdf6f2012-04-10 16:50:55 -0700144 * Controls how the alt key is handled.
145 *
146 * escape....... Send an ESC prefix.
147 * 8-bit........ Add 128 to the unshifted character as in xterm.
148 * browser-key.. Wait for the keypress event and see what the browser says.
149 * (This won't work well on platforms where the browser
150 * performs a default action for some alt sequences.)
rginda30f20f62012-04-05 16:36:19 -0700151 */
rginda39bdf6f2012-04-10 16:50:55 -0700152 ['alt-sends-what', 'escape', function(v) {
153 if (!/^(escape|8-bit|browser-key)$/.test(v))
154 v = 'escape';
155
156 self.vt.keyboard.altSendsWhat = v;
rginda30f20f62012-04-05 16:36:19 -0700157 }
158 ],
159
160 /**
161 * Terminal bell sound. Empty string for no audible bell.
162 */
163 ['audible-bell-sound', '../audio/bell.ogg', function(v) {
164 self.bellAudio_.setAttribute('src', v);
165 }
166 ],
167
168 /**
169 * The background color for text with no other color attributes.
170 */
171 ['background-color', 'rgb(16, 16, 16)', function(v) {
rginda9f5222b2012-03-05 11:53:28 -0800172 self.scrollPort_.setBackgroundColor(v);
173 }
174 ],
175
176 /**
rginda30f20f62012-04-05 16:36:19 -0700177 * The background image.
178 *
179 * Defaults to a subtle light-to-transparent-to-dark gradient that is
180 * mostly transparent.
181 */
182 ['background-image',
183 ('-webkit-linear-gradient(bottom, ' +
184 'rgba(0,0,0,0.01) 0%, ' +
185 'rgba(0,0,0,0) 30%, ' +
186 'rgba(255,255,255,0) 70%, ' +
187 'rgba(255,255,255,0.05) 100%)'),
188 function(v) {
189 self.scrollPort_.setBackgroundImage(v);
190 }
191 ],
192
193 /**
194 * If true, the backspace should send BS ('\x08', aka ^H). Otherwise
195 * the backspace key should send '\x7f'.
196 */
197 ['backspace-sends-backspace', false, function(v) {
198 self.keyboard.backspaceSendsBackspace = v;
199 }
200 ],
201
202 /**
rgindade84e382012-04-20 15:39:31 -0700203 * Whether or not to blink the cursor by default.
204 */
205 ['cursor-blink', false, function(v) {
206 self.setCursorBlink(!!v);
207 }
208 ],
209
210 /**
rginda30f20f62012-04-05 16:36:19 -0700211 * The color of the visible cursor.
212 */
213 ['cursor-color', 'rgba(255,0,0,0.5)', function(v) {
214 self.cursorNode_.style.backgroundColor = v;
215 }
216 ],
217
218 /**
219 * True if we should use bold weight font for text with the bold/bright
220 * attribute. False to use bright colors only. Null to autodetect.
221 */
222 ['enable-bold', null, function(v) {
223 self.syncBoldSafeState();
224 }
225 ],
226
227 /**
rginda9f5222b2012-03-05 11:53:28 -0800228 * Default font family for the terminal text.
229 */
230 ['font-family', ('"DejaVu Sans Mono", "Everson Mono", ' +
231 'FreeMono, "Menlo", "Lucida Console", ' +
232 'monospace'),
233 function(v) { self.syncFontFamily() }
234 ],
235
236 /**
rginda30f20f62012-04-05 16:36:19 -0700237 * The default font size in pixels.
238 */
239 ['font-size', 15, function(v) {
240 self.setFontSize(v);
241 }
242 ],
243
244 /**
rginda9f5222b2012-03-05 11:53:28 -0800245 * Anti-aliasing.
246 */
247 ['font-smoothing', 'antialiased',
248 function(v) { self.syncFontFamily() }
249 ],
250
251 /**
rginda30f20f62012-04-05 16:36:19 -0700252 * The foreground color for text with no other color attributes.
rginda9f5222b2012-03-05 11:53:28 -0800253 */
rginda30f20f62012-04-05 16:36:19 -0700254 ['foreground-color', 'rgb(240, 240, 240)', function(v) {
255 self.scrollPort_.setForegroundColor(v);
rginda9f5222b2012-03-05 11:53:28 -0800256 }
257 ],
258
259 /**
rginda30f20f62012-04-05 16:36:19 -0700260 * If true, home/end will control the terminal scrollbar and shift home/end
261 * will send the VT keycodes. If false then home/end sends VT codes and
262 * shift home/end scrolls.
rginda9f5222b2012-03-05 11:53:28 -0800263 */
rginda30f20f62012-04-05 16:36:19 -0700264 ['home-keys-scroll', false, function(v) {
265 self.keyboard.homeKeysScroll = v;
266 }
267 ],
268
269 /**
270 * Set whether the meta key sends a leading escape or not.
271 */
272 ['meta-sends-escape', true, function(v) {
273 self.keyboard.metaSendsEscape = v;
rginda9f5222b2012-03-05 11:53:28 -0800274 }
275 ],
276
277 /**
278 * If true, scroll to the bottom on any keystroke.
279 */
280 ['scroll-on-keystroke', true, function(v) {
281 self.scrollOnKeystroke_ = v;
282 }
283 ],
284
285 /**
286 * If true, scroll to the bottom on terminal output.
287 */
288 ['scroll-on-output', false, function(v) {
289 self.scrollOnOutput_ = v;
290 }
291 ],
292
293 /**
David Reveman8f552492012-03-28 12:18:41 -0400294 * The vertical scrollbar mode.
295 */
296 ['scrollbar-visible', true, function(v) {
297 self.setScrollbarVisible(v);
298 }
299 ],
rginda30f20f62012-04-05 16:36:19 -0700300
301 /**
rgindaf522ce02012-04-17 17:49:17 -0700302 * The default environment variables.
303 */
304 ['environment', {TERM: 'xterm-256color'}, null],
305
306 /**
rginda30f20f62012-04-05 16:36:19 -0700307 * If true, page up/down will control the terminal scrollbar and shift
308 * page up/down will send the VT keycodes. If false then page up/down
309 * sends VT codes and shift page up/down scrolls.
310 */
311 ['page-keys-scroll', false, function(v) {
312 self.keyboard.pageKeysScroll = v;
313 }
314 ],
315
rginda9f5222b2012-03-05 11:53:28 -0800316 ]);
317
318 if (needSync)
319 this.prefs_.notifyAll();
320};
321
322/**
323 * Return the current terminal background color.
324 *
325 * Intended for use by other classes, so we don't have to expose the entire
326 * prefs_ object.
327 */
328hterm.Terminal.prototype.getBackgroundColor = function() {
329 return this.prefs_.get('background-color');
330};
331
332/**
333 * Return the current terminal foreground color.
334 *
335 * Intended for use by other classes, so we don't have to expose the entire
336 * prefs_ object.
337 */
338hterm.Terminal.prototype.getForegroundColor = function() {
339 return this.prefs_.get('foreground-color');
340};
341
342/**
rginda87b86462011-12-14 13:48:03 -0800343 * Create a new instance of a terminal command and run it with a given
344 * argument string.
345 *
346 * @param {function} commandClass The constructor for a terminal command.
347 * @param {string} argString The argument string to pass to the command.
348 */
349hterm.Terminal.prototype.runCommandClass = function(commandClass, argString) {
rgindaf522ce02012-04-17 17:49:17 -0700350 var environment = this.prefs_.get('environment');
351 if (typeof environment != 'object' || environment == null)
352 environment = {};
353
rginda87b86462011-12-14 13:48:03 -0800354 var self = this;
355 this.command = new commandClass(
356 { argString: argString || '',
357 io: this.io.push(),
rgindaf522ce02012-04-17 17:49:17 -0700358 environment: environment,
rginda87b86462011-12-14 13:48:03 -0800359 onExit: function(code) {
360 self.io.pop();
361 self.io.println(hterm.msg('COMMAND_COMPLETE',
362 [self.command.commandName, code]));
rgindafeaf3142012-01-31 15:14:20 -0800363 self.uninstallKeyboard();
rginda87b86462011-12-14 13:48:03 -0800364 }
365 });
366
rgindafeaf3142012-01-31 15:14:20 -0800367 this.installKeyboard();
rginda87b86462011-12-14 13:48:03 -0800368 this.command.run();
369};
370
371/**
rgindafeaf3142012-01-31 15:14:20 -0800372 * Returns true if the current screen is the primary screen, false otherwise.
373 */
374hterm.Terminal.prototype.isPrimaryScreen = function() {
rgindaf522ce02012-04-17 17:49:17 -0700375 return this.screen_ == this.primaryScreen_;
rgindafeaf3142012-01-31 15:14:20 -0800376};
377
378/**
379 * Install the keyboard handler for this terminal.
380 *
381 * This will prevent the browser from seeing any keystrokes sent to the
382 * terminal.
383 */
384hterm.Terminal.prototype.installKeyboard = function() {
385 this.keyboard.installKeyboard(this.document_.body.firstChild);
386}
387
388/**
389 * Uninstall the keyboard handler for this terminal.
390 */
391hterm.Terminal.prototype.uninstallKeyboard = function() {
392 this.keyboard.installKeyboard(null);
393}
394
395/**
rginda35c456b2012-02-09 17:29:05 -0800396 * Set the font size for this terminal.
rginda9f5222b2012-03-05 11:53:28 -0800397 *
398 * Call setFontSize(0) to reset to the default font size.
399 *
400 * This function does not modify the font-size preference.
401 *
402 * @param {number} px The desired font size, in pixels.
rginda35c456b2012-02-09 17:29:05 -0800403 */
404hterm.Terminal.prototype.setFontSize = function(px) {
rginda9f5222b2012-03-05 11:53:28 -0800405 if (px === 0)
406 px = this.prefs_.get('font-size');
407
rginda35c456b2012-02-09 17:29:05 -0800408 this.scrollPort_.setFontSize(px);
409};
410
411/**
412 * Get the current font size.
413 */
414hterm.Terminal.prototype.getFontSize = function() {
415 return this.scrollPort_.getFontSize();
416};
417
418/**
419 * Set the CSS "font-family" for this terminal.
420 */
rginda9f5222b2012-03-05 11:53:28 -0800421hterm.Terminal.prototype.syncFontFamily = function() {
422 this.scrollPort_.setFontFamily(this.prefs_.get('font-family'),
423 this.prefs_.get('font-smoothing'));
424 this.syncBoldSafeState();
425};
426
427hterm.Terminal.prototype.syncBoldSafeState = function() {
428 var enableBold = this.prefs_.get('enable-bold');
429 if (enableBold !== null) {
430 this.screen_.textAttributes.enableBold = enableBold;
431 return;
432 }
433
rgindaf7521392012-02-28 17:20:34 -0800434 var normalSize = this.scrollPort_.measureCharacterSize();
435 var boldSize = this.scrollPort_.measureCharacterSize('bold');
436
437 var isBoldSafe = normalSize.equals(boldSize);
rgindaf7521392012-02-28 17:20:34 -0800438 if (!isBoldSafe) {
439 console.warn('Bold characters disabled: Size of bold weight differs ' +
rgindac9759de2012-03-19 13:21:41 -0700440 'from normal. Font family is: ' +
441 this.scrollPort_.getFontFamily());
rgindaf7521392012-02-28 17:20:34 -0800442 }
rginda9f5222b2012-03-05 11:53:28 -0800443
444 this.screen_.textAttributes.enableBold = isBoldSafe;
rginda35c456b2012-02-09 17:29:05 -0800445};
446
447/**
rginda87b86462011-12-14 13:48:03 -0800448 * Return a copy of the current cursor position.
449 *
450 * @return {hterm.RowCol} The RowCol object representing the current position.
451 */
452hterm.Terminal.prototype.saveCursor = function() {
453 return this.screen_.cursorPosition.clone();
454};
455
rgindaa19afe22012-01-25 15:40:22 -0800456hterm.Terminal.prototype.getTextAttributes = function() {
457 return this.screen_.textAttributes;
458};
459
rginda87b86462011-12-14 13:48:03 -0800460/**
rgindaf522ce02012-04-17 17:49:17 -0700461 * Return the current browser zoom factor applied to the terminal.
462 *
463 * @return {number} The current browser zoom factor.
464 */
465hterm.Terminal.prototype.getZoomFactor = function() {
466 return this.scrollPort_.characterSize.zoomFactor;
467};
468
469/**
rginda9846e2f2012-01-27 13:53:33 -0800470 * Change the title of this terminal's window.
471 */
472hterm.Terminal.prototype.setWindowTitle = function(title) {
rgindafeaf3142012-01-31 15:14:20 -0800473 window.document.title = title;
rginda9846e2f2012-01-27 13:53:33 -0800474};
475
476/**
rginda87b86462011-12-14 13:48:03 -0800477 * Restore a previously saved cursor position.
478 *
479 * @param {hterm.RowCol} cursor The position to restore.
480 */
481hterm.Terminal.prototype.restoreCursor = function(cursor) {
rginda35c456b2012-02-09 17:29:05 -0800482 var row = hterm.clamp(cursor.row, 0, this.screenSize.height - 1);
483 var column = hterm.clamp(cursor.column, 0, this.screenSize.width - 1);
484 this.screen_.setCursorPosition(row, column);
485 if (cursor.column > column ||
486 cursor.column == column && cursor.overflow) {
487 this.screen_.cursorPosition.overflow = true;
488 }
rginda87b86462011-12-14 13:48:03 -0800489};
490
491/**
492 * Set the width of the terminal, resizing the UI to match.
493 */
494hterm.Terminal.prototype.setWidth = function(columnCount) {
rgindaf0090c92012-02-10 14:58:52 -0800495 if (columnCount == null) {
496 this.div_.style.width = '100%';
497 return;
498 }
499
rginda35c456b2012-02-09 17:29:05 -0800500 this.div_.style.width = this.scrollPort_.characterSize.width *
501 columnCount + this.scrollbarWidthPx + 'px';
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +0400502 this.realizeSize_(columnCount, this.screenSize.height);
rgindac9bc5502012-01-18 11:48:44 -0800503 this.scheduleSyncCursorPosition_();
504};
rginda87b86462011-12-14 13:48:03 -0800505
rgindac9bc5502012-01-18 11:48:44 -0800506/**
rginda35c456b2012-02-09 17:29:05 -0800507 * Set the height of the terminal, resizing the UI to match.
508 */
509hterm.Terminal.prototype.setHeight = function(rowCount) {
rgindaf0090c92012-02-10 14:58:52 -0800510 if (rowCount == null) {
511 this.div_.style.height = '100%';
512 return;
513 }
514
rginda35c456b2012-02-09 17:29:05 -0800515 this.div_.style.height =
rginda30f20f62012-04-05 16:36:19 -0700516 this.scrollPort_.characterSize.height * rowCount + 'px';
rginda35c456b2012-02-09 17:29:05 -0800517 this.realizeSize_(this.screenSize.width, rowCount);
518 this.scheduleSyncCursorPosition_();
519};
520
521/**
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +0400522 * Deal with terminal size changes.
523 *
524 */
525hterm.Terminal.prototype.realizeSize_ = function(columnCount, rowCount) {
526 if (columnCount != this.screenSize.width)
527 this.realizeWidth_(columnCount);
528
529 if (rowCount != this.screenSize.height)
530 this.realizeHeight_(rowCount);
531
532 // Send new terminal size to plugin.
533 this.io.onTerminalResize(columnCount, rowCount);
534};
535
536/**
rgindac9bc5502012-01-18 11:48:44 -0800537 * Deal with terminal width changes.
538 *
539 * This function does what needs to be done when the terminal width changes
540 * out from under us. It happens here rather than in onResize_() because this
541 * code may need to run synchronously to handle programmatic changes of
542 * terminal width.
543 *
544 * Relying on the browser to send us an async resize event means we may not be
545 * in the correct state yet when the next escape sequence hits.
546 */
547hterm.Terminal.prototype.realizeWidth_ = function(columnCount) {
548 var deltaColumns = columnCount - this.screen_.getWidth();
549
rginda87b86462011-12-14 13:48:03 -0800550 this.screenSize.width = columnCount;
551 this.screen_.setColumnCount(columnCount);
rgindac9bc5502012-01-18 11:48:44 -0800552
553 if (deltaColumns > 0) {
554 this.setDefaultTabStops(this.screenSize.width - deltaColumns);
555 } else {
556 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
557 if (this.tabStops_[i] <= columnCount)
558 break;
559
560 this.tabStops_.pop();
561 }
562 }
563
564 this.screen_.setColumnCount(this.screenSize.width);
565};
566
567/**
568 * Deal with terminal height changes.
569 *
570 * This function does what needs to be done when the terminal height changes
571 * out from under us. It happens here rather than in onResize_() because this
572 * code may need to run synchronously to handle programmatic changes of
573 * terminal height.
574 *
575 * Relying on the browser to send us an async resize event means we may not be
576 * in the correct state yet when the next escape sequence hits.
577 */
578hterm.Terminal.prototype.realizeHeight_ = function(rowCount) {
579 var deltaRows = rowCount - this.screen_.getHeight();
580
581 this.screenSize.height = rowCount;
582
583 var cursor = this.saveCursor();
584
585 if (deltaRows < 0) {
586 // Screen got smaller.
587 deltaRows *= -1;
588 while (deltaRows) {
589 var lastRow = this.getRowCount() - 1;
590 if (lastRow - this.scrollbackRows_.length == cursor.row)
591 break;
592
593 if (this.getRowText(lastRow))
594 break;
595
596 this.screen_.popRow();
597 deltaRows--;
598 }
599
600 var ary = this.screen_.shiftRows(deltaRows);
601 this.scrollbackRows_.push.apply(this.scrollbackRows_, ary);
602
603 // We just removed rows from the top of the screen, we need to update
604 // the cursor to match.
rginda35c456b2012-02-09 17:29:05 -0800605 cursor.row = Math.max(cursor.row - deltaRows, 0);
rgindac9bc5502012-01-18 11:48:44 -0800606 } else if (deltaRows > 0) {
607 // Screen got larger.
608
609 if (deltaRows <= this.scrollbackRows_.length) {
610 var scrollbackCount = Math.min(deltaRows, this.scrollbackRows_.length);
611 var rows = this.scrollbackRows_.splice(
612 this.scrollbackRows_.length - scrollbackCount, scrollbackCount);
613 this.screen_.unshiftRows(rows);
614 deltaRows -= scrollbackCount;
615 cursor.row += scrollbackCount;
616 }
617
618 if (deltaRows)
619 this.appendRows_(deltaRows);
620 }
621
rginda35c456b2012-02-09 17:29:05 -0800622 this.setVTScrollRegion(null, null);
rgindac9bc5502012-01-18 11:48:44 -0800623 this.restoreCursor(cursor);
rginda87b86462011-12-14 13:48:03 -0800624};
625
626/**
627 * Scroll the terminal to the top of the scrollback buffer.
628 */
629hterm.Terminal.prototype.scrollHome = function() {
630 this.scrollPort_.scrollRowToTop(0);
631};
632
633/**
634 * Scroll the terminal to the end.
635 */
636hterm.Terminal.prototype.scrollEnd = function() {
637 this.scrollPort_.scrollRowToBottom(this.getRowCount());
638};
639
640/**
641 * Scroll the terminal one page up (minus one line) relative to the current
642 * position.
643 */
644hterm.Terminal.prototype.scrollPageUp = function() {
645 var i = this.scrollPort_.getTopRowIndex();
646 this.scrollPort_.scrollRowToTop(i - this.screenSize.height + 1);
647};
648
649/**
650 * Scroll the terminal one page down (minus one line) relative to the current
651 * position.
652 */
653hterm.Terminal.prototype.scrollPageDown = function() {
654 var i = this.scrollPort_.getTopRowIndex();
655 this.scrollPort_.scrollRowToTop(i + this.screenSize.height - 1);
rginda8ba33642011-12-14 12:31:31 -0800656};
657
rgindac9bc5502012-01-18 11:48:44 -0800658/**
659 * Full terminal reset.
660 */
rginda87b86462011-12-14 13:48:03 -0800661hterm.Terminal.prototype.reset = function() {
rgindac9bc5502012-01-18 11:48:44 -0800662 this.clearAllTabStops();
663 this.setDefaultTabStops();
rgindac9bc5502012-01-18 11:48:44 -0800664 this.setVTScrollRegion(null, null);
rginda9ea433c2012-03-16 11:57:00 -0700665
666 this.clearHome(this.primaryScreen_);
667 this.primaryScreen_.textAttributes.reset();
668
669 this.clearHome(this.alternateScreen_);
670 this.alternateScreen_.textAttributes.reset();
671
rgindac9bc5502012-01-18 11:48:44 -0800672 this.softReset();
rginda87b86462011-12-14 13:48:03 -0800673};
674
rgindac9bc5502012-01-18 11:48:44 -0800675/**
676 * Soft terminal reset.
677 */
rginda0f5c0292012-01-13 11:00:13 -0800678hterm.Terminal.prototype.softReset = function() {
rgindac9bc5502012-01-18 11:48:44 -0800679 this.options_ = new hterm.Options();
rgindaf522ce02012-04-17 17:49:17 -0700680
681 this.primaryScreen_.textAttributes.resetColorPalette();
682 this.alternateScreen_.textAttributes.resetColorPalette();
683
rgindaa19afe22012-01-25 15:40:22 -0800684 this.setCursorVisible(true);
rgindade84e382012-04-20 15:39:31 -0700685 this.setCursorBlink(!!this.prefs_.get('cursor-blink'));
rginda0f5c0292012-01-13 11:00:13 -0800686};
687
rgindac9bc5502012-01-18 11:48:44 -0800688/**
689 * Move the cursor forward to the next tab stop, or to the last column
690 * if no more tab stops are set.
691 */
692hterm.Terminal.prototype.forwardTabStop = function() {
693 var column = this.screen_.cursorPosition.column;
694
695 for (var i = 0; i < this.tabStops_.length; i++) {
696 if (this.tabStops_[i] > column) {
697 this.setCursorColumn(this.tabStops_[i]);
698 return;
699 }
700 }
701
702 this.setCursorColumn(this.screenSize.width - 1);
rginda0f5c0292012-01-13 11:00:13 -0800703};
704
rgindac9bc5502012-01-18 11:48:44 -0800705/**
706 * Move the cursor backward to the previous tab stop, or to the first column
707 * if no previous tab stops are set.
708 */
709hterm.Terminal.prototype.backwardTabStop = function() {
710 var column = this.screen_.cursorPosition.column;
711
712 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
713 if (this.tabStops_[i] < column) {
714 this.setCursorColumn(this.tabStops_[i]);
715 return;
716 }
717 }
718
719 this.setCursorColumn(1);
rginda0f5c0292012-01-13 11:00:13 -0800720};
721
rgindac9bc5502012-01-18 11:48:44 -0800722/**
723 * Set a tab stop at the given column.
724 *
725 * @param {int} column Zero based column.
726 */
727hterm.Terminal.prototype.setTabStop = function(column) {
728 for (var i = this.tabStops_.length - 1; i >= 0; i--) {
729 if (this.tabStops_[i] == column)
730 return;
731
732 if (this.tabStops_[i] < column) {
733 this.tabStops_.splice(i + 1, 0, column);
734 return;
735 }
736 }
737
738 this.tabStops_.splice(0, 0, column);
rginda87b86462011-12-14 13:48:03 -0800739};
740
rgindac9bc5502012-01-18 11:48:44 -0800741/**
742 * Clear the tab stop at the current cursor position.
743 *
744 * No effect if there is no tab stop at the current cursor position.
745 */
746hterm.Terminal.prototype.clearTabStopAtCursor = function() {
747 var column = this.screen_.cursorPosition.column;
748
749 var i = this.tabStops_.indexOf(column);
750 if (i == -1)
751 return;
752
753 this.tabStops_.splice(i, 1);
754};
755
756/**
757 * Clear all tab stops.
758 */
759hterm.Terminal.prototype.clearAllTabStops = function() {
760 this.tabStops_.length = 0;
761};
762
763/**
764 * Set up the default tab stops, starting from a given column.
765 *
766 * This sets a tabstop every (column % this.tabWidth) column, starting
767 * from the specified column, or 0 if no column is provided.
768 *
769 * This does not clear the existing tab stops first, use clearAllTabStops
770 * for that.
771 *
772 * @param {int} opt_start Optional starting zero based starting column, useful
773 * for filling out missing tab stops when the terminal is resized.
774 */
775hterm.Terminal.prototype.setDefaultTabStops = function(opt_start) {
776 var start = opt_start || 0;
777 var w = this.tabWidth;
778 var stopCount = Math.floor((this.screenSize.width - start) / this.tabWidth)
779 for (var i = 0; i < stopCount; i++) {
780 this.setTabStop(Math.floor((start + i * w) / w) * w + w);
781 }
rginda87b86462011-12-14 13:48:03 -0800782};
783
rginda6d397402012-01-17 10:58:29 -0800784/**
785 * Save cursor position and attributes.
786 *
787 * TODO(rginda): Save attributes once we support them.
788 */
rginda87b86462011-12-14 13:48:03 -0800789hterm.Terminal.prototype.saveOptions = function() {
rginda6d397402012-01-17 10:58:29 -0800790 this.savedOptions_.cursor = this.saveCursor();
rgindaa19afe22012-01-25 15:40:22 -0800791 this.savedOptions_.textAttributes = this.screen_.textAttributes.clone();
rginda87b86462011-12-14 13:48:03 -0800792};
793
rginda6d397402012-01-17 10:58:29 -0800794/**
795 * Restore cursor position and attributes.
796 *
797 * TODO(rginda): Restore attributes once we support them.
798 */
rginda87b86462011-12-14 13:48:03 -0800799hterm.Terminal.prototype.restoreOptions = function() {
rginda6d397402012-01-17 10:58:29 -0800800 if (this.savedOptions_.cursor)
801 this.restoreCursor(this.savedOptions_.cursor);
rgindaa19afe22012-01-25 15:40:22 -0800802 if (this.savedOptions_.textAttributes)
803 this.screen_.textAttributes = this.savedOptions_.textAttributes;
rginda8ba33642011-12-14 12:31:31 -0800804};
805
806/**
807 * Interpret a sequence of characters.
808 *
809 * Incomplete escape sequences are buffered until the next call.
810 *
811 * @param {string} str Sequence of characters to interpret or pass through.
812 */
813hterm.Terminal.prototype.interpret = function(str) {
rginda0f5c0292012-01-13 11:00:13 -0800814 this.vt.interpret(str);
rginda8ba33642011-12-14 12:31:31 -0800815 this.scheduleSyncCursorPosition_();
816};
817
818/**
819 * Take over the given DIV for use as the terminal display.
820 *
821 * @param {HTMLDivElement} div The div to use as the terminal display.
822 */
823hterm.Terminal.prototype.decorate = function(div) {
rginda87b86462011-12-14 13:48:03 -0800824 this.div_ = div;
825
rginda8ba33642011-12-14 12:31:31 -0800826 this.scrollPort_.decorate(div);
rginda30f20f62012-04-05 16:36:19 -0700827 this.scrollPort_.setBackgroundImage(this.prefs_.get('background-image'));
828
rginda0918b652012-04-04 11:26:24 -0700829 this.div_.focus = this.focus.bind(this);
rgindaf7521392012-02-28 17:20:34 -0800830
rginda9f5222b2012-03-05 11:53:28 -0800831 this.setFontSize(this.prefs_.get('font-size'));
832 this.syncFontFamily();
rgindaa19afe22012-01-25 15:40:22 -0800833
David Reveman8f552492012-03-28 12:18:41 -0400834 this.setScrollbarVisible(this.prefs_.get('scrollbar-visible'));
835
rginda8ba33642011-12-14 12:31:31 -0800836 this.document_ = this.scrollPort_.getDocument();
837
rginda8ba33642011-12-14 12:31:31 -0800838 this.cursorNode_ = this.document_.createElement('div');
839 this.cursorNode_.style.cssText =
840 ('position: absolute;' +
rginda87b86462011-12-14 13:48:03 -0800841 'top: -99px;' +
842 'display: block;' +
rginda35c456b2012-02-09 17:29:05 -0800843 'width: ' + this.scrollPort_.characterSize.width + 'px;' +
844 'height: ' + this.scrollPort_.characterSize.height + 'px;' +
rginda6d397402012-01-17 10:58:29 -0800845 '-webkit-transition: opacity, background-color 100ms linear;' +
rginda9f5222b2012-03-05 11:53:28 -0800846 'background-color: ' + this.prefs_.get('cursor-color'));
rginda8ba33642011-12-14 12:31:31 -0800847 this.document_.body.appendChild(this.cursorNode_);
848
rgindade84e382012-04-20 15:39:31 -0700849 this.setCursorBlink(!!this.prefs_.get('cursor-blink'));
rginda8ba33642011-12-14 12:31:31 -0800850 this.setReverseVideo(false);
rginda87b86462011-12-14 13:48:03 -0800851
rginda87b86462011-12-14 13:48:03 -0800852 this.scrollPort_.focus();
rginda6d397402012-01-17 10:58:29 -0800853 this.scrollPort_.scheduleRedraw();
rginda87b86462011-12-14 13:48:03 -0800854};
855
rginda0918b652012-04-04 11:26:24 -0700856/**
857 * Return the HTML document that contains the terminal DOM nodes.
858 */
rginda87b86462011-12-14 13:48:03 -0800859hterm.Terminal.prototype.getDocument = function() {
860 return this.document_;
rginda8ba33642011-12-14 12:31:31 -0800861};
862
863/**
rginda0918b652012-04-04 11:26:24 -0700864 * Focus the terminal.
865 */
866hterm.Terminal.prototype.focus = function() {
867 this.scrollPort_.focus();
868};
869
870/**
rginda8ba33642011-12-14 12:31:31 -0800871 * Return the HTML Element for a given row index.
872 *
873 * This is a method from the RowProvider interface. The ScrollPort uses
874 * it to fetch rows on demand as they are scrolled into view.
875 *
876 * TODO(rginda): Consider saving scrollback rows as (HTML source, text content)
877 * pairs to conserve memory.
878 *
879 * @param {integer} index The zero-based row index, measured relative to the
880 * start of the scrollback buffer. On-screen rows will always have the
881 * largest indicies.
882 * @return {HTMLElement} The 'x-row' element containing for the requested row.
883 */
884hterm.Terminal.prototype.getRowNode = function(index) {
885 if (index < this.scrollbackRows_.length)
886 return this.scrollbackRows_[index];
887
888 var screenIndex = index - this.scrollbackRows_.length;
889 return this.screen_.rowsArray[screenIndex];
890};
891
892/**
893 * Return the text content for a given range of rows.
894 *
895 * This is a method from the RowProvider interface. The ScrollPort uses
896 * it to fetch text content on demand when the user attempts to copy their
897 * selection to the clipboard.
898 *
899 * @param {integer} start The zero-based row index to start from, measured
900 * relative to the start of the scrollback buffer. On-screen rows will
901 * always have the largest indicies.
902 * @param {integer} end The zero-based row index to end on, measured
903 * relative to the start of the scrollback buffer.
904 * @return {string} A single string containing the text value of the range of
905 * rows. Lines will be newline delimited, with no trailing newline.
906 */
907hterm.Terminal.prototype.getRowsText = function(start, end) {
908 var ary = [];
909 for (var i = start; i < end; i++) {
910 var node = this.getRowNode(i);
911 ary.push(node.textContent);
912 }
913
914 return ary.join('\n');
915};
916
917/**
918 * Return the text content for a given row.
919 *
920 * This is a method from the RowProvider interface. The ScrollPort uses
921 * it to fetch text content on demand when the user attempts to copy their
922 * selection to the clipboard.
923 *
924 * @param {integer} index The zero-based row index to return, measured
925 * relative to the start of the scrollback buffer. On-screen rows will
926 * always have the largest indicies.
927 * @return {string} A string containing the text value of the selected row.
928 */
929hterm.Terminal.prototype.getRowText = function(index) {
930 var node = this.getRowNode(index);
rginda87b86462011-12-14 13:48:03 -0800931 return node.textContent;
rginda8ba33642011-12-14 12:31:31 -0800932};
933
934/**
935 * Return the total number of rows in the addressable screen and in the
936 * scrollback buffer of this terminal.
937 *
938 * This is a method from the RowProvider interface. The ScrollPort uses
939 * it to compute the size of the scrollbar.
940 *
941 * @return {integer} The number of rows in this terminal.
942 */
943hterm.Terminal.prototype.getRowCount = function() {
944 return this.scrollbackRows_.length + this.screen_.rowsArray.length;
945};
946
947/**
948 * Create DOM nodes for new rows and append them to the end of the terminal.
949 *
950 * This is the only correct way to add a new DOM node for a row. Notice that
951 * the new row is appended to the bottom of the list of rows, and does not
952 * require renumbering (of the rowIndex property) of previous rows.
953 *
954 * If you think you want a new blank row somewhere in the middle of the
955 * terminal, look into moveRows_().
956 *
957 * This method does not pay attention to vtScrollTop/Bottom, since you should
958 * be using moveRows() in cases where they would matter.
959 *
960 * The cursor will be positioned at column 0 of the first inserted line.
961 */
962hterm.Terminal.prototype.appendRows_ = function(count) {
963 var cursorRow = this.screen_.rowsArray.length;
964 var offset = this.scrollbackRows_.length + cursorRow;
965 for (var i = 0; i < count; i++) {
966 var row = this.document_.createElement('x-row');
967 row.appendChild(this.document_.createTextNode(''));
968 row.rowIndex = offset + i;
969 this.screen_.pushRow(row);
970 }
971
972 var extraRows = this.screen_.rowsArray.length - this.screenSize.height;
973 if (extraRows > 0) {
974 var ary = this.screen_.shiftRows(extraRows);
975 Array.prototype.push.apply(this.scrollbackRows_, ary);
976 this.scheduleScrollDown_();
977 }
978
979 if (cursorRow >= this.screen_.rowsArray.length)
980 cursorRow = this.screen_.rowsArray.length - 1;
981
rginda87b86462011-12-14 13:48:03 -0800982 this.setAbsoluteCursorPosition(cursorRow, 0);
rginda8ba33642011-12-14 12:31:31 -0800983};
984
985/**
986 * Relocate rows from one part of the addressable screen to another.
987 *
988 * This is used to recycle rows during VT scrolls (those which are driven
989 * by VT commands, rather than by the user manipulating the scrollbar.)
990 *
991 * In this case, the blank lines scrolled into the scroll region are made of
992 * the nodes we scrolled off. These have their rowIndex properties carefully
993 * renumbered so as not to confuse the ScrollPort.
rginda8ba33642011-12-14 12:31:31 -0800994 */
995hterm.Terminal.prototype.moveRows_ = function(fromIndex, count, toIndex) {
996 var ary = this.screen_.removeRows(fromIndex, count);
997 this.screen_.insertRows(toIndex, ary);
998
999 var start, end;
1000 if (fromIndex < toIndex) {
1001 start = fromIndex;
rginda87b86462011-12-14 13:48:03 -08001002 end = toIndex + count;
rginda8ba33642011-12-14 12:31:31 -08001003 } else {
1004 start = toIndex;
rginda87b86462011-12-14 13:48:03 -08001005 end = fromIndex + count;
rginda8ba33642011-12-14 12:31:31 -08001006 }
1007
1008 this.renumberRows_(start, end);
rginda2312fff2012-01-05 16:20:52 -08001009 this.scrollPort_.scheduleInvalidate();
rginda8ba33642011-12-14 12:31:31 -08001010};
1011
1012/**
1013 * Renumber the rowIndex property of the given range of rows.
1014 *
1015 * The start and end indicies are relative to the screen, not the scrollback.
1016 * Rows in the scrollback buffer cannot be renumbered. Since they are not
rginda2312fff2012-01-05 16:20:52 -08001017 * addressable (you can't delete them, scroll them, etc), you should have
rginda8ba33642011-12-14 12:31:31 -08001018 * no need to renumber scrollback rows.
1019 */
1020hterm.Terminal.prototype.renumberRows_ = function(start, end) {
1021 var offset = this.scrollbackRows_.length;
1022 for (var i = start; i < end; i++) {
1023 this.screen_.rowsArray[i].rowIndex = offset + i;
1024 }
1025};
1026
1027/**
1028 * Print a string to the terminal.
1029 *
1030 * This respects the current insert and wraparound modes. It will add new lines
1031 * to the end of the terminal, scrolling off the top into the scrollback buffer
1032 * if necessary.
1033 *
1034 * The string is *not* parsed for escape codes. Use the interpret() method if
1035 * that's what you're after.
1036 *
1037 * @param{string} str The string to print.
1038 */
1039hterm.Terminal.prototype.print = function(str) {
rgindaa19afe22012-01-25 15:40:22 -08001040 if (this.options_.wraparound && this.screen_.cursorPosition.overflow)
1041 this.newLine();
rginda2312fff2012-01-05 16:20:52 -08001042
rgindaa19afe22012-01-25 15:40:22 -08001043 if (this.options_.insertMode) {
1044 this.screen_.insertString(str);
1045 } else {
1046 this.screen_.overwriteString(str);
1047 }
1048
1049 var overflow = this.screen_.maybeClipCurrentRow();
1050
1051 if (this.options_.wraparound && overflow) {
1052 var lastColumn;
1053
1054 do {
rginda35c456b2012-02-09 17:29:05 -08001055 this.newLine();
1056 lastColumn = overflow.characterLength;
rgindaa19afe22012-01-25 15:40:22 -08001057
1058 if (!this.options_.insertMode)
1059 this.screen_.deleteChars(overflow.characterLength);
1060
1061 this.screen_.prependNodes(overflow);
rgindaa19afe22012-01-25 15:40:22 -08001062
1063 overflow = this.screen_.maybeClipCurrentRow();
1064 } while (overflow);
1065
1066 this.setCursorColumn(lastColumn);
1067 }
rginda8ba33642011-12-14 12:31:31 -08001068
1069 this.scheduleSyncCursorPosition_();
rginda0f5c0292012-01-13 11:00:13 -08001070
rginda9f5222b2012-03-05 11:53:28 -08001071 if (this.scrollOnOutput_)
rginda0f5c0292012-01-13 11:00:13 -08001072 this.scrollPort_.scrollRowToBottom(this.getRowCount());
rginda8ba33642011-12-14 12:31:31 -08001073};
1074
1075/**
rginda87b86462011-12-14 13:48:03 -08001076 * Set the VT scroll region.
1077 *
rginda87b86462011-12-14 13:48:03 -08001078 * This also resets the cursor position to the absolute (0, 0) position, since
1079 * that's what xterm appears to do.
1080 *
1081 * @param {integer} scrollTop The zero-based top of the scroll region.
1082 * @param {integer} scrollBottom The zero-based bottom of the scroll region,
1083 * inclusive.
1084 */
1085hterm.Terminal.prototype.setVTScrollRegion = function(scrollTop, scrollBottom) {
1086 this.vtScrollTop_ = scrollTop;
1087 this.vtScrollBottom_ = scrollBottom;
rginda87b86462011-12-14 13:48:03 -08001088};
1089
1090/**
rginda8ba33642011-12-14 12:31:31 -08001091 * Return the top row index according to the VT.
1092 *
1093 * This will return 0 unless the terminal has been told to restrict scrolling
1094 * to some lower row. It is used for some VT cursor positioning and scrolling
1095 * commands.
1096 *
1097 * @return {integer} The topmost row in the terminal's scroll region.
1098 */
1099hterm.Terminal.prototype.getVTScrollTop = function() {
1100 if (this.vtScrollTop_ != null)
1101 return this.vtScrollTop_;
1102
1103 return 0;
rginda87b86462011-12-14 13:48:03 -08001104};
rginda8ba33642011-12-14 12:31:31 -08001105
1106/**
1107 * Return the bottom row index according to the VT.
1108 *
1109 * This will return the height of the terminal unless the it has been told to
1110 * restrict scrolling to some higher row. It is used for some VT cursor
1111 * positioning and scrolling commands.
1112 *
1113 * @return {integer} The bottommost row in the terminal's scroll region.
1114 */
1115hterm.Terminal.prototype.getVTScrollBottom = function() {
1116 if (this.vtScrollBottom_ != null)
1117 return this.vtScrollBottom_;
1118
rginda87b86462011-12-14 13:48:03 -08001119 return this.screenSize.height - 1;
rginda8ba33642011-12-14 12:31:31 -08001120}
1121
1122/**
1123 * Process a '\n' character.
1124 *
1125 * If the cursor is on the final row of the terminal this will append a new
1126 * blank row to the screen and scroll the topmost row into the scrollback
1127 * buffer.
1128 *
1129 * Otherwise, this moves the cursor to column zero of the next row.
1130 */
1131hterm.Terminal.prototype.newLine = function() {
1132 if (this.screen_.cursorPosition.row == this.screen_.rowsArray.length - 1) {
rginda87b86462011-12-14 13:48:03 -08001133 // If we're at the end of the screen we need to append a new line and
1134 // scroll the top line into the scrollback buffer.
rginda8ba33642011-12-14 12:31:31 -08001135 this.appendRows_(1);
rginda87b86462011-12-14 13:48:03 -08001136 } else if (this.screen_.cursorPosition.row == this.getVTScrollBottom()) {
1137 // End of the scroll region does not affect the scrollback buffer.
1138 this.vtScrollUp(1);
1139 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, 0);
rginda8ba33642011-12-14 12:31:31 -08001140 } else {
rginda87b86462011-12-14 13:48:03 -08001141 // Anywhere else in the screen just moves the cursor.
1142 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row + 1, 0);
rginda8ba33642011-12-14 12:31:31 -08001143 }
1144};
1145
1146/**
1147 * Like newLine(), except maintain the cursor column.
1148 */
1149hterm.Terminal.prototype.lineFeed = function() {
1150 var column = this.screen_.cursorPosition.column;
1151 this.newLine();
1152 this.setCursorColumn(column);
1153};
1154
1155/**
rginda87b86462011-12-14 13:48:03 -08001156 * If autoCarriageReturn is set then newLine(), else lineFeed().
1157 */
1158hterm.Terminal.prototype.formFeed = function() {
1159 if (this.options_.autoCarriageReturn) {
1160 this.newLine();
1161 } else {
1162 this.lineFeed();
1163 }
1164};
1165
1166/**
1167 * Move the cursor up one row, possibly inserting a blank line.
1168 *
1169 * The cursor column is not changed.
1170 */
1171hterm.Terminal.prototype.reverseLineFeed = function() {
1172 var scrollTop = this.getVTScrollTop();
1173 var currentRow = this.screen_.cursorPosition.row;
1174
1175 if (currentRow == scrollTop) {
1176 this.insertLines(1);
1177 } else {
1178 this.setAbsoluteCursorRow(currentRow - 1);
1179 }
1180};
1181
1182/**
rginda8ba33642011-12-14 12:31:31 -08001183 * Replace all characters to the left of the current cursor with the space
1184 * character.
1185 *
1186 * TODO(rginda): This should probably *remove* the characters (not just replace
1187 * with a space) if there are no characters at or beyond the current cursor
1188 * position. Once it does that, it'll have the same text-attribute related
1189 * issues as hterm.Screen.prototype.clearCursorRow :/
1190 */
1191hterm.Terminal.prototype.eraseToLeft = function() {
rginda87b86462011-12-14 13:48:03 -08001192 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001193 this.setCursorColumn(0);
rginda87b86462011-12-14 13:48:03 -08001194 this.screen_.overwriteString(hterm.getWhitespace(cursor.column + 1));
1195 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001196};
1197
1198/**
1199 * Erase a given number of characters to the right of the cursor, shifting
1200 * remaining characters to the left.
1201 *
1202 * The cursor position is unchanged.
1203 *
1204 * TODO(rginda): Test that this works even when the cursor is positioned beyond
1205 * the end of the text.
1206 *
1207 * TODO(rginda): This likely has text-attribute related troubles similar to the
1208 * todo on hterm.Screen.prototype.clearCursorRow.
1209 */
1210hterm.Terminal.prototype.eraseToRight = function(opt_count) {
rginda87b86462011-12-14 13:48:03 -08001211 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001212
rginda87b86462011-12-14 13:48:03 -08001213 var maxCount = this.screenSize.width - cursor.column;
rginda8ba33642011-12-14 12:31:31 -08001214 var count = (opt_count && opt_count < maxCount) ? opt_count : maxCount;
1215 this.screen_.deleteChars(count);
rginda87b86462011-12-14 13:48:03 -08001216 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001217};
1218
1219/**
1220 * Erase the current line.
1221 *
1222 * The cursor position is unchanged.
1223 *
1224 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1225 * has a text-attribute related TODO.
1226 */
1227hterm.Terminal.prototype.eraseLine = function() {
rginda87b86462011-12-14 13:48:03 -08001228 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001229 this.screen_.clearCursorRow();
rginda87b86462011-12-14 13:48:03 -08001230 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001231};
1232
1233/**
1234 * Erase all characters from the start of the scroll region to the current
1235 * cursor position.
1236 *
1237 * The cursor position is unchanged.
1238 *
1239 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1240 * has a text-attribute related TODO.
1241 */
1242hterm.Terminal.prototype.eraseAbove = function() {
rginda87b86462011-12-14 13:48:03 -08001243 var cursor = this.saveCursor();
1244
1245 this.eraseToLeft();
rginda8ba33642011-12-14 12:31:31 -08001246
1247 var top = this.getVTScrollTop();
rginda87b86462011-12-14 13:48:03 -08001248 for (var i = top; i < cursor.row; i++) {
1249 this.setAbsoluteCursorPosition(i, 0);
rginda8ba33642011-12-14 12:31:31 -08001250 this.screen_.clearCursorRow();
1251 }
1252
rginda87b86462011-12-14 13:48:03 -08001253 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001254};
1255
1256/**
1257 * Erase all characters from the current cursor position to the end of the
1258 * scroll region.
1259 *
1260 * The cursor position is unchanged.
1261 *
1262 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1263 * has a text-attribute related TODO.
1264 */
1265hterm.Terminal.prototype.eraseBelow = function() {
rginda87b86462011-12-14 13:48:03 -08001266 var cursor = this.saveCursor();
1267
1268 this.eraseToRight();
rginda8ba33642011-12-14 12:31:31 -08001269
1270 var bottom = this.getVTScrollBottom();
rginda87b86462011-12-14 13:48:03 -08001271 for (var i = cursor.row + 1; i <= bottom; i++) {
1272 this.setAbsoluteCursorPosition(i, 0);
rginda8ba33642011-12-14 12:31:31 -08001273 this.screen_.clearCursorRow();
1274 }
1275
rginda87b86462011-12-14 13:48:03 -08001276 this.restoreCursor(cursor);
1277};
1278
1279/**
1280 * Fill the terminal with a given character.
1281 *
1282 * This methods does not respect the VT scroll region.
1283 *
1284 * @param {string} ch The character to use for the fill.
1285 */
1286hterm.Terminal.prototype.fill = function(ch) {
1287 var cursor = this.saveCursor();
1288
1289 this.setAbsoluteCursorPosition(0, 0);
1290 for (var row = 0; row < this.screenSize.height; row++) {
1291 for (var col = 0; col < this.screenSize.width; col++) {
1292 this.setAbsoluteCursorPosition(row, col);
1293 this.screen_.overwriteString(ch);
1294 }
1295 }
1296
1297 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001298};
1299
1300/**
rginda9ea433c2012-03-16 11:57:00 -07001301 * Erase the entire display and leave the cursor at (0, 0).
rginda8ba33642011-12-14 12:31:31 -08001302 *
rginda9ea433c2012-03-16 11:57:00 -07001303 * This does not respect the scroll region.
1304 *
1305 * @param {hterm.Screen} opt_screen Optional screen to operate on. Defaults
1306 * to the current screen.
rginda8ba33642011-12-14 12:31:31 -08001307 *
1308 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1309 * has a text-attribute related TODO.
1310 */
rginda9ea433c2012-03-16 11:57:00 -07001311hterm.Terminal.prototype.clearHome = function(opt_screen) {
1312 var screen = opt_screen || this.screen_;
1313 var bottom = screen.getHeight();
rginda8ba33642011-12-14 12:31:31 -08001314
rgindae4d29232012-01-19 10:47:13 -08001315 for (var i = 0; i < bottom; i++) {
rginda9ea433c2012-03-16 11:57:00 -07001316 screen.setCursorPosition(i, 0);
1317 screen.clearCursorRow();
rginda8ba33642011-12-14 12:31:31 -08001318 }
1319
rginda9ea433c2012-03-16 11:57:00 -07001320 screen.setCursorPosition(0, 0);
1321};
1322
1323/**
1324 * Erase the entire display without changing the cursor position.
1325 *
1326 * The cursor position is unchanged. This does not respect the scroll
1327 * region.
1328 *
1329 * @param {hterm.Screen} opt_screen Optional screen to operate on. Defaults
1330 * to the current screen.
1331 *
1332 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1333 * has a text-attribute related TODO.
1334 */
1335hterm.Terminal.prototype.clear = function(opt_screen) {
1336 var screen = opt_screen || this.screen_;
1337 var cursor = screen.cursorPosition.clone();
1338 this.clearHome(screen);
1339 screen.setCursorPosition(cursor.row, cursor.column);
rginda8ba33642011-12-14 12:31:31 -08001340};
1341
1342/**
1343 * VT command to insert lines at the current cursor row.
1344 *
1345 * This respects the current scroll region. Rows pushed off the bottom are
1346 * lost (they won't show up in the scrollback buffer).
1347 *
1348 * TODO(rginda): This relies on hterm.Screen.prototype.clearCursorRow, which
1349 * has a text-attribute related TODO.
1350 *
1351 * @param {integer} count The number of lines to insert.
1352 */
1353hterm.Terminal.prototype.insertLines = function(count) {
rginda87b86462011-12-14 13:48:03 -08001354 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001355
1356 var bottom = this.getVTScrollBottom();
rginda87b86462011-12-14 13:48:03 -08001357 count = Math.min(count, bottom - cursor.row);
rginda8ba33642011-12-14 12:31:31 -08001358
rgindae4d29232012-01-19 10:47:13 -08001359 var start = bottom - count + 1;
rginda87b86462011-12-14 13:48:03 -08001360 if (start != cursor.row)
1361 this.moveRows_(start, count, cursor.row);
rginda8ba33642011-12-14 12:31:31 -08001362
1363 for (var i = 0; i < count; i++) {
rginda87b86462011-12-14 13:48:03 -08001364 this.setAbsoluteCursorPosition(cursor.row + i, 0);
rginda8ba33642011-12-14 12:31:31 -08001365 this.screen_.clearCursorRow();
1366 }
1367
rginda87b86462011-12-14 13:48:03 -08001368 cursor.column = 0;
1369 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001370};
1371
1372/**
1373 * VT command to delete lines at the current cursor row.
1374 *
1375 * New rows are added to the bottom of scroll region to take their place. New
1376 * rows are strictly there to take up space and have no content or style.
1377 */
1378hterm.Terminal.prototype.deleteLines = function(count) {
rginda87b86462011-12-14 13:48:03 -08001379 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001380
rginda87b86462011-12-14 13:48:03 -08001381 var top = cursor.row;
rginda8ba33642011-12-14 12:31:31 -08001382 var bottom = this.getVTScrollBottom();
1383
rginda87b86462011-12-14 13:48:03 -08001384 var maxCount = bottom - top + 1;
rginda8ba33642011-12-14 12:31:31 -08001385 count = Math.min(count, maxCount);
1386
rginda87b86462011-12-14 13:48:03 -08001387 var moveStart = bottom - count + 1;
rginda8ba33642011-12-14 12:31:31 -08001388 if (count != maxCount)
1389 this.moveRows_(top, count, moveStart);
1390
1391 for (var i = 0; i < count; i++) {
rginda87b86462011-12-14 13:48:03 -08001392 this.setAbsoluteCursorPosition(moveStart + i, 0);
rginda8ba33642011-12-14 12:31:31 -08001393 this.screen_.clearCursorRow();
1394 }
1395
rginda87b86462011-12-14 13:48:03 -08001396 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001397};
1398
1399/**
1400 * Inserts the given number of spaces at the current cursor position.
1401 *
rginda87b86462011-12-14 13:48:03 -08001402 * The cursor position is not changed.
rginda8ba33642011-12-14 12:31:31 -08001403 */
1404hterm.Terminal.prototype.insertSpace = function(count) {
rginda87b86462011-12-14 13:48:03 -08001405 var cursor = this.saveCursor();
1406
rginda0f5c0292012-01-13 11:00:13 -08001407 var ws = hterm.getWhitespace(count || 1);
rginda8ba33642011-12-14 12:31:31 -08001408 this.screen_.insertString(ws);
rgindaa19afe22012-01-25 15:40:22 -08001409 this.screen_.maybeClipCurrentRow();
rginda87b86462011-12-14 13:48:03 -08001410
1411 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001412};
1413
1414/**
1415 * Forward-delete the specified number of characters starting at the cursor
1416 * position.
1417 *
1418 * @param {integer} count The number of characters to delete.
1419 */
1420hterm.Terminal.prototype.deleteChars = function(count) {
1421 this.screen_.deleteChars(count);
1422};
1423
1424/**
1425 * Shift rows in the scroll region upwards by a given number of lines.
1426 *
1427 * New rows are inserted at the bottom of the scroll region to fill the
1428 * vacated rows. The new rows not filled out with the current text attributes.
1429 *
1430 * This function does not affect the scrollback rows at all. Rows shifted
1431 * off the top are lost.
1432 *
rginda87b86462011-12-14 13:48:03 -08001433 * The cursor position is not altered.
1434 *
rginda8ba33642011-12-14 12:31:31 -08001435 * @param {integer} count The number of rows to scroll.
1436 */
1437hterm.Terminal.prototype.vtScrollUp = function(count) {
rginda87b86462011-12-14 13:48:03 -08001438 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001439
rginda87b86462011-12-14 13:48:03 -08001440 this.setAbsoluteCursorRow(this.getVTScrollTop());
rginda8ba33642011-12-14 12:31:31 -08001441 this.deleteLines(count);
1442
rginda87b86462011-12-14 13:48:03 -08001443 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001444};
1445
1446/**
1447 * Shift rows below the cursor down by a given number of lines.
1448 *
1449 * This function respects the current scroll region.
1450 *
1451 * New rows are inserted at the top of the scroll region to fill the
1452 * vacated rows. The new rows not filled out with the current text attributes.
1453 *
1454 * This function does not affect the scrollback rows at all. Rows shifted
1455 * off the bottom are lost.
1456 *
1457 * @param {integer} count The number of rows to scroll.
1458 */
1459hterm.Terminal.prototype.vtScrollDown = function(opt_count) {
rginda87b86462011-12-14 13:48:03 -08001460 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001461
rginda87b86462011-12-14 13:48:03 -08001462 this.setAbsoluteCursorPosition(this.getVTScrollTop(), 0);
rginda8ba33642011-12-14 12:31:31 -08001463 this.insertLines(opt_count);
1464
rginda87b86462011-12-14 13:48:03 -08001465 this.restoreCursor(cursor);
rginda8ba33642011-12-14 12:31:31 -08001466};
1467
rginda87b86462011-12-14 13:48:03 -08001468
rginda8ba33642011-12-14 12:31:31 -08001469/**
1470 * Set the cursor position.
1471 *
1472 * The cursor row is relative to the scroll region if the terminal has
1473 * 'origin mode' enabled, or relative to the addressable screen otherwise.
1474 *
1475 * @param {integer} row The new zero-based cursor row.
1476 * @param {integer} row The new zero-based cursor column.
1477 */
1478hterm.Terminal.prototype.setCursorPosition = function(row, column) {
1479 if (this.options_.originMode) {
rginda87b86462011-12-14 13:48:03 -08001480 this.setRelativeCursorPosition(row, column);
rginda8ba33642011-12-14 12:31:31 -08001481 } else {
rginda87b86462011-12-14 13:48:03 -08001482 this.setAbsoluteCursorPosition(row, column);
rginda8ba33642011-12-14 12:31:31 -08001483 }
rginda87b86462011-12-14 13:48:03 -08001484};
rginda8ba33642011-12-14 12:31:31 -08001485
rginda87b86462011-12-14 13:48:03 -08001486hterm.Terminal.prototype.setRelativeCursorPosition = function(row, column) {
1487 var scrollTop = this.getVTScrollTop();
1488 row = hterm.clamp(row + scrollTop, scrollTop, this.getVTScrollBottom());
rginda2312fff2012-01-05 16:20:52 -08001489 column = hterm.clamp(column, 0, this.screenSize.width - 1);
rginda87b86462011-12-14 13:48:03 -08001490 this.screen_.setCursorPosition(row, column);
1491};
1492
1493hterm.Terminal.prototype.setAbsoluteCursorPosition = function(row, column) {
rginda2312fff2012-01-05 16:20:52 -08001494 row = hterm.clamp(row, 0, this.screenSize.height - 1);
1495 column = hterm.clamp(column, 0, this.screenSize.width - 1);
rginda8ba33642011-12-14 12:31:31 -08001496 this.screen_.setCursorPosition(row, column);
1497};
1498
1499/**
1500 * Set the cursor column.
1501 *
1502 * @param {integer} column The new zero-based cursor column.
1503 */
1504hterm.Terminal.prototype.setCursorColumn = function(column) {
rginda87b86462011-12-14 13:48:03 -08001505 this.setAbsoluteCursorPosition(this.screen_.cursorPosition.row, column);
rginda8ba33642011-12-14 12:31:31 -08001506};
1507
1508/**
1509 * Return the cursor column.
1510 *
1511 * @return {integer} The zero-based cursor column.
1512 */
1513hterm.Terminal.prototype.getCursorColumn = function() {
1514 return this.screen_.cursorPosition.column;
1515};
1516
1517/**
1518 * Set the cursor row.
1519 *
1520 * The cursor row is relative to the scroll region if the terminal has
1521 * 'origin mode' enabled, or relative to the addressable screen otherwise.
1522 *
1523 * @param {integer} row The new cursor row.
1524 */
rginda87b86462011-12-14 13:48:03 -08001525hterm.Terminal.prototype.setAbsoluteCursorRow = function(row) {
1526 this.setAbsoluteCursorPosition(row, this.screen_.cursorPosition.column);
rginda8ba33642011-12-14 12:31:31 -08001527};
1528
1529/**
1530 * Return the cursor row.
1531 *
1532 * @return {integer} The zero-based cursor row.
1533 */
1534hterm.Terminal.prototype.getCursorRow = function(row) {
1535 return this.screen_.cursorPosition.row;
1536};
1537
1538/**
1539 * Request that the ScrollPort redraw itself soon.
1540 *
1541 * The redraw will happen asynchronously, soon after the call stack winds down.
1542 * Multiple calls will be coalesced into a single redraw.
1543 */
1544hterm.Terminal.prototype.scheduleRedraw_ = function() {
rginda87b86462011-12-14 13:48:03 -08001545 if (this.timeouts_.redraw)
1546 return;
rginda8ba33642011-12-14 12:31:31 -08001547
1548 var self = this;
rginda87b86462011-12-14 13:48:03 -08001549 this.timeouts_.redraw = setTimeout(function() {
1550 delete self.timeouts_.redraw;
rginda8ba33642011-12-14 12:31:31 -08001551 self.scrollPort_.redraw_();
1552 }, 0);
1553};
1554
1555/**
1556 * Request that the ScrollPort be scrolled to the bottom.
1557 *
1558 * The scroll will happen asynchronously, soon after the call stack winds down.
1559 * Multiple calls will be coalesced into a single scroll.
1560 *
1561 * This affects the scrollbar position of the ScrollPort, and has nothing to
1562 * do with the VT scroll commands.
1563 */
1564hterm.Terminal.prototype.scheduleScrollDown_ = function() {
1565 if (this.timeouts_.scrollDown)
rginda87b86462011-12-14 13:48:03 -08001566 return;
rginda8ba33642011-12-14 12:31:31 -08001567
1568 var self = this;
1569 this.timeouts_.scrollDown = setTimeout(function() {
1570 delete self.timeouts_.scrollDown;
1571 self.scrollPort_.scrollRowToBottom(self.getRowCount());
1572 }, 10);
1573};
1574
1575/**
1576 * Move the cursor up a specified number of rows.
1577 *
1578 * @param {integer} count The number of rows to move the cursor.
1579 */
1580hterm.Terminal.prototype.cursorUp = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001581 return this.cursorDown(-(count || 1));
rginda8ba33642011-12-14 12:31:31 -08001582};
1583
1584/**
1585 * Move the cursor down a specified number of rows.
1586 *
1587 * @param {integer} count The number of rows to move the cursor.
1588 */
1589hterm.Terminal.prototype.cursorDown = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001590 count = count || 1;
rginda8ba33642011-12-14 12:31:31 -08001591 var minHeight = (this.options_.originMode ? this.getVTScrollTop() : 0);
1592 var maxHeight = (this.options_.originMode ? this.getVTScrollBottom() :
1593 this.screenSize.height - 1);
1594
1595 var row = hterm.clamp(this.screen_.cursorPosition.row + count,
1596 minHeight, maxHeight);
rginda87b86462011-12-14 13:48:03 -08001597 this.setAbsoluteCursorRow(row);
rginda8ba33642011-12-14 12:31:31 -08001598};
1599
1600/**
1601 * Move the cursor left a specified number of columns.
1602 *
1603 * @param {integer} count The number of columns to move the cursor.
1604 */
1605hterm.Terminal.prototype.cursorLeft = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001606 return this.cursorRight(-(count || 1));
rginda8ba33642011-12-14 12:31:31 -08001607};
1608
1609/**
1610 * Move the cursor right a specified number of columns.
1611 *
1612 * @param {integer} count The number of columns to move the cursor.
1613 */
1614hterm.Terminal.prototype.cursorRight = function(count) {
rginda0f5c0292012-01-13 11:00:13 -08001615 count = count || 1;
rginda8ba33642011-12-14 12:31:31 -08001616 var column = hterm.clamp(this.screen_.cursorPosition.column + count,
rginda87b86462011-12-14 13:48:03 -08001617 0, this.screenSize.width - 1);
rginda8ba33642011-12-14 12:31:31 -08001618 this.setCursorColumn(column);
1619};
1620
1621/**
1622 * Reverse the foreground and background colors of the terminal.
1623 *
1624 * This only affects text that was drawn with no attributes.
1625 *
1626 * TODO(rginda): Test xterm to see if reverse is respected for text that has
1627 * been drawn with attributes that happen to coincide with the default
1628 * 'no-attribute' colors. My guess is probably not.
1629 */
1630hterm.Terminal.prototype.setReverseVideo = function(state) {
rginda87b86462011-12-14 13:48:03 -08001631 this.options_.reverseVideo = state;
rginda8ba33642011-12-14 12:31:31 -08001632 if (state) {
rginda9f5222b2012-03-05 11:53:28 -08001633 this.scrollPort_.setForegroundColor(this.prefs_.get('background-color'));
1634 this.scrollPort_.setBackgroundColor(this.prefs_.get('foreground-color'));
rginda8ba33642011-12-14 12:31:31 -08001635 } else {
rginda9f5222b2012-03-05 11:53:28 -08001636 this.scrollPort_.setForegroundColor(this.prefs_.get('foreground-color'));
1637 this.scrollPort_.setBackgroundColor(this.prefs_.get('background-color'));
rginda8ba33642011-12-14 12:31:31 -08001638 }
1639};
1640
1641/**
rginda87b86462011-12-14 13:48:03 -08001642 * Ring the terminal bell.
rginda87b86462011-12-14 13:48:03 -08001643 */
1644hterm.Terminal.prototype.ringBell = function() {
rginda9f5222b2012-03-05 11:53:28 -08001645 if (this.bellAudio_.getAttribute('src'))
1646 this.bellAudio_.play();
rgindaf0090c92012-02-10 14:58:52 -08001647
rginda6d397402012-01-17 10:58:29 -08001648 this.cursorNode_.style.backgroundColor =
1649 this.scrollPort_.getForegroundColor();
rginda87b86462011-12-14 13:48:03 -08001650
1651 var self = this;
1652 setTimeout(function() {
rginda9f5222b2012-03-05 11:53:28 -08001653 self.cursorNode_.style.backgroundColor = self.prefs_.get('cursor-color');
rginda6d397402012-01-17 10:58:29 -08001654 }, 200);
rginda87b86462011-12-14 13:48:03 -08001655};
1656
1657/**
rginda8ba33642011-12-14 12:31:31 -08001658 * Set the origin mode bit.
1659 *
1660 * If origin mode is on, certain VT cursor and scrolling commands measure their
1661 * row parameter relative to the VT scroll region. Otherwise, row 0 corresponds
1662 * to the top of the addressable screen.
1663 *
1664 * Defaults to off.
1665 *
1666 * @param {boolean} state True to set origin mode, false to unset.
1667 */
1668hterm.Terminal.prototype.setOriginMode = function(state) {
1669 this.options_.originMode = state;
rgindae4d29232012-01-19 10:47:13 -08001670 this.setCursorPosition(0, 0);
rginda8ba33642011-12-14 12:31:31 -08001671};
1672
1673/**
1674 * Set the insert mode bit.
1675 *
1676 * If insert mode is on, existing text beyond the cursor position will be
1677 * shifted right to make room for new text. Otherwise, new text overwrites
1678 * any existing text.
1679 *
1680 * Defaults to off.
1681 *
1682 * @param {boolean} state True to set insert mode, false to unset.
1683 */
1684hterm.Terminal.prototype.setInsertMode = function(state) {
1685 this.options_.insertMode = state;
1686};
1687
1688/**
rginda87b86462011-12-14 13:48:03 -08001689 * Set the auto carriage return bit.
1690 *
1691 * If auto carriage return is on then a formfeed character is interpreted
1692 * as a newline, otherwise it's the same as a linefeed. The difference boils
1693 * down to whether or not the cursor column is reset.
1694 */
1695hterm.Terminal.prototype.setAutoCarriageReturn = function(state) {
1696 this.options_.autoCarriageReturn = state;
1697};
1698
1699/**
rginda8ba33642011-12-14 12:31:31 -08001700 * Set the wraparound mode bit.
1701 *
1702 * If wraparound mode is on, certain VT commands will allow the cursor to wrap
1703 * to the start of the following row. Otherwise, the cursor is clamped to the
1704 * end of the screen and attempts to write past it are ignored.
1705 *
1706 * Defaults to on.
1707 *
1708 * @param {boolean} state True to set wraparound mode, false to unset.
1709 */
1710hterm.Terminal.prototype.setWraparound = function(state) {
1711 this.options_.wraparound = state;
1712};
1713
1714/**
1715 * Set the reverse-wraparound mode bit.
1716 *
1717 * If wraparound mode is off, certain VT commands will allow the cursor to wrap
1718 * to the end of the previous row. Otherwise, the cursor is clamped to column
1719 * 0.
1720 *
1721 * Defaults to off.
1722 *
1723 * @param {boolean} state True to set reverse-wraparound mode, false to unset.
1724 */
1725hterm.Terminal.prototype.setReverseWraparound = function(state) {
1726 this.options_.reverseWraparound = state;
1727};
1728
1729/**
1730 * Selects between the primary and alternate screens.
1731 *
1732 * If alternate mode is on, the alternate screen is active. Otherwise the
1733 * primary screen is active.
1734 *
1735 * Swapping screens has no effect on the scrollback buffer.
1736 *
1737 * Each screen maintains its own cursor position.
1738 *
1739 * Defaults to off.
1740 *
1741 * @param {boolean} state True to set alternate mode, false to unset.
1742 */
1743hterm.Terminal.prototype.setAlternateMode = function(state) {
rginda6d397402012-01-17 10:58:29 -08001744 var cursor = this.saveCursor();
rginda8ba33642011-12-14 12:31:31 -08001745 this.screen_ = state ? this.alternateScreen_ : this.primaryScreen_;
1746
rginda35c456b2012-02-09 17:29:05 -08001747 if (this.screen_.rowsArray.length &&
1748 this.screen_.rowsArray[0].rowIndex != this.scrollbackRows_.length) {
1749 // If the screen changed sizes while we were away, our rowIndexes may
1750 // be incorrect.
1751 var offset = this.scrollbackRows_.length;
1752 var ary = this.screen_.rowsArray;
1753 for (i = 0; i < ary.length; i++) {
1754 ary[i].rowIndex = offset + i;
1755 }
1756 }
rginda8ba33642011-12-14 12:31:31 -08001757
rginda35c456b2012-02-09 17:29:05 -08001758 this.realizeWidth_(this.screenSize.width);
1759 this.realizeHeight_(this.screenSize.height);
1760 this.scrollPort_.syncScrollHeight();
1761 this.scrollPort_.invalidate();
rginda8ba33642011-12-14 12:31:31 -08001762
rginda6d397402012-01-17 10:58:29 -08001763 this.restoreCursor(cursor);
rginda35c456b2012-02-09 17:29:05 -08001764 this.scrollPort_.resize();
rginda8ba33642011-12-14 12:31:31 -08001765};
1766
1767/**
1768 * Set the cursor-blink mode bit.
1769 *
1770 * If cursor-blink is on, the cursor will blink when it is visible. Otherwise
1771 * a visible cursor does not blink.
1772 *
1773 * You should make sure to turn blinking off if you're going to dispose of a
1774 * terminal, otherwise you'll leak a timeout.
1775 *
1776 * Defaults to on.
1777 *
1778 * @param {boolean} state True to set cursor-blink mode, false to unset.
1779 */
1780hterm.Terminal.prototype.setCursorBlink = function(state) {
1781 this.options_.cursorBlink = state;
1782
1783 if (!state && this.timeouts_.cursorBlink) {
1784 clearTimeout(this.timeouts_.cursorBlink);
1785 delete this.timeouts_.cursorBlink;
1786 }
1787
1788 if (this.options_.cursorVisible)
1789 this.setCursorVisible(true);
1790};
1791
1792/**
1793 * Set the cursor-visible mode bit.
1794 *
1795 * If cursor-visible is on, the cursor will be visible. Otherwise it will not.
1796 *
1797 * Defaults to on.
1798 *
1799 * @param {boolean} state True to set cursor-visible mode, false to unset.
1800 */
1801hterm.Terminal.prototype.setCursorVisible = function(state) {
1802 this.options_.cursorVisible = state;
1803
1804 if (!state) {
rginda87b86462011-12-14 13:48:03 -08001805 this.cursorNode_.style.opacity = '0';
rginda8ba33642011-12-14 12:31:31 -08001806 return;
1807 }
1808
rginda87b86462011-12-14 13:48:03 -08001809 this.syncCursorPosition_();
1810
1811 this.cursorNode_.style.opacity = '1';
rginda8ba33642011-12-14 12:31:31 -08001812
1813 if (this.options_.cursorBlink) {
1814 if (this.timeouts_.cursorBlink)
1815 return;
1816
1817 this.timeouts_.cursorBlink = setInterval(this.onCursorBlink_.bind(this),
1818 500);
1819 } else {
1820 if (this.timeouts_.cursorBlink) {
1821 clearTimeout(this.timeouts_.cursorBlink);
1822 delete this.timeouts_.cursorBlink;
1823 }
1824 }
1825};
1826
1827/**
rginda87b86462011-12-14 13:48:03 -08001828 * Synchronizes the visible cursor and document selection with the current
1829 * cursor coordinates.
rginda8ba33642011-12-14 12:31:31 -08001830 */
1831hterm.Terminal.prototype.syncCursorPosition_ = function() {
1832 var topRowIndex = this.scrollPort_.getTopRowIndex();
1833 var bottomRowIndex = this.scrollPort_.getBottomRowIndex(topRowIndex);
1834 var cursorRowIndex = this.scrollbackRows_.length +
1835 this.screen_.cursorPosition.row;
1836
1837 if (cursorRowIndex > bottomRowIndex) {
1838 // Cursor is scrolled off screen, move it outside of the visible area.
rginda35c456b2012-02-09 17:29:05 -08001839 this.cursorNode_.style.top = -this.scrollPort_.characterSize.height + 'px';
rginda8ba33642011-12-14 12:31:31 -08001840 return;
1841 }
1842
rginda35c456b2012-02-09 17:29:05 -08001843 this.cursorNode_.style.width = this.scrollPort_.characterSize.width + 'px';
1844 this.cursorNode_.style.height = this.scrollPort_.characterSize.height + 'px';
1845
rginda8ba33642011-12-14 12:31:31 -08001846 this.cursorNode_.style.top = this.scrollPort_.visibleRowTopMargin +
rginda35c456b2012-02-09 17:29:05 -08001847 this.scrollPort_.characterSize.height * (cursorRowIndex - topRowIndex) +
1848 'px';
1849 this.cursorNode_.style.left = this.scrollPort_.characterSize.width *
1850 this.screen_.cursorPosition.column + 'px';
rginda87b86462011-12-14 13:48:03 -08001851
1852 this.cursorNode_.setAttribute('title',
1853 '(' + this.screen_.cursorPosition.row +
1854 ', ' + this.screen_.cursorPosition.column +
1855 ')');
1856
1857 // Update the caret for a11y purposes.
1858 var selection = this.document_.getSelection();
1859 if (selection && selection.isCollapsed)
1860 this.screen_.syncSelectionCaret(selection);
rginda8ba33642011-12-14 12:31:31 -08001861};
1862
1863/**
1864 * Synchronizes the visible cursor with the current cursor coordinates.
1865 *
1866 * The sync will happen asynchronously, soon after the call stack winds down.
1867 * Multiple calls will be coalesced into a single sync.
1868 */
1869hterm.Terminal.prototype.scheduleSyncCursorPosition_ = function() {
1870 if (this.timeouts_.syncCursor)
rginda87b86462011-12-14 13:48:03 -08001871 return;
rginda8ba33642011-12-14 12:31:31 -08001872
1873 var self = this;
1874 this.timeouts_.syncCursor = setTimeout(function() {
1875 self.syncCursorPosition_();
1876 delete self.timeouts_.syncCursor;
rginda87b86462011-12-14 13:48:03 -08001877 }, 0);
1878};
1879
rgindacc2996c2012-02-24 14:59:31 -08001880/**
rgindaf522ce02012-04-17 17:49:17 -07001881 * Show or hide the zoom warning.
1882 *
1883 * The zoom warning is a message warning the user that their browser zoom must
1884 * be set to 100% in order for hterm to function properly.
1885 *
1886 * @param {boolean} state True to show the message, false to hide it.
1887 */
1888hterm.Terminal.prototype.showZoomWarning_ = function(state) {
1889 if (!this.zoomWarningNode_) {
1890 if (!state)
1891 return;
1892
1893 this.zoomWarningNode_ = this.document_.createElement('div');
1894 this.zoomWarningNode_.style.cssText = (
1895 'color: black;' +
1896 'background-color: #ff2222;' +
1897 'font-size: large;' +
1898 'border-radius: 8px;' +
1899 'opacity: 0.75;' +
1900 'padding: 0.2em 0.5em 0.2em 0.5em;' +
1901 'top: 0.5em;' +
1902 'right: 1.2em;' +
1903 'position: absolute;' +
1904 '-webkit-text-size-adjust: none;' +
1905 '-webkit-user-select: none;');
rgindaf522ce02012-04-17 17:49:17 -07001906 }
1907
rgindade84e382012-04-20 15:39:31 -07001908 this.zoomWarningNode_.textContent = hterm.msg('ZOOM_WARNING') ||
1909 ('!! ' + parseInt(this.scrollPort_.characterSize.zoomFactor * 100) +
1910 '% !!');
rgindaf522ce02012-04-17 17:49:17 -07001911 this.zoomWarningNode_.style.fontFamily = this.prefs_.get('font-family');
1912
1913 if (state) {
1914 if (!this.zoomWarningNode_.parentNode)
1915 this.div_.parentNode.appendChild(this.zoomWarningNode_);
1916 } else if (this.zoomWarningNode_.parentNode) {
1917 this.zoomWarningNode_.parentNode.removeChild(this.zoomWarningNode_);
1918 }
1919};
1920
1921/**
rgindacc2996c2012-02-24 14:59:31 -08001922 * Show the terminal overlay for a given amount of time.
1923 *
1924 * The terminal overlay appears in inverse video in a large font, centered
1925 * over the terminal. You should probably keep the overlay message brief,
1926 * since it's in a large font and you probably aren't going to check the size
1927 * of the terminal first.
1928 *
1929 * @param {string} msg The text (not HTML) message to display in the overlay.
1930 * @param {number} opt_timeout The amount of time to wait before fading out
1931 * the overlay. Defaults to 1.5 seconds. Pass null to have the overlay
1932 * stay up forever (or until the next overlay).
1933 */
1934hterm.Terminal.prototype.showOverlay = function(msg, opt_timeout) {
rgindaf0090c92012-02-10 14:58:52 -08001935 if (!this.overlayNode_) {
1936 if (!this.div_)
1937 return;
1938
1939 this.overlayNode_ = this.document_.createElement('div');
1940 this.overlayNode_.style.cssText = (
rgindaf0090c92012-02-10 14:58:52 -08001941 'border-radius: 15px;' +
rgindaf0090c92012-02-10 14:58:52 -08001942 'font-size: xx-large;' +
1943 'opacity: 0.75;' +
1944 'padding: 0.2em 0.5em 0.2em 0.5em;' +
1945 'position: absolute;' +
1946 '-webkit-user-select: none;' +
1947 '-webkit-transition: opacity 180ms ease-in;');
1948 }
1949
rginda9f5222b2012-03-05 11:53:28 -08001950 this.overlayNode_.style.color = this.prefs_.get('background-color');
1951 this.overlayNode_.style.backgroundColor = this.prefs_.get('foreground-color');
1952 this.overlayNode_.style.fontFamily = this.prefs_.get('font-family');
1953
rgindaf0090c92012-02-10 14:58:52 -08001954 this.overlayNode_.textContent = msg;
1955 this.overlayNode_.style.opacity = '0.75';
1956
1957 if (!this.overlayNode_.parentNode)
1958 this.div_.appendChild(this.overlayNode_);
1959
1960 this.overlayNode_.style.top = (
1961 this.div_.clientHeight - this.overlayNode_.clientHeight) / 2;
1962 this.overlayNode_.style.left = (
1963 this.div_.clientWidth - this.overlayNode_.clientWidth -
1964 this.scrollbarWidthPx) / 2;
1965
1966 var self = this;
1967
1968 if (this.overlayTimeout_)
1969 clearTimeout(this.overlayTimeout_);
1970
rgindacc2996c2012-02-24 14:59:31 -08001971 if (opt_timeout === null)
1972 return;
1973
rgindaf0090c92012-02-10 14:58:52 -08001974 this.overlayTimeout_ = setTimeout(function() {
1975 self.overlayNode_.style.opacity = '0';
1976 setTimeout(function() {
rginda259dcca2012-03-14 16:37:11 -07001977 if (self.overlayNode_.parentNode)
1978 self.overlayNode_.parentNode.removeChild(self.overlayNode_);
rgindaf0090c92012-02-10 14:58:52 -08001979 self.overlayTimeout_ = null;
1980 self.overlayNode_.style.opacity = '0.75';
1981 }, 200);
rgindacc2996c2012-02-24 14:59:31 -08001982 }, opt_timeout || 1500);
rgindaf0090c92012-02-10 14:58:52 -08001983};
1984
1985hterm.Terminal.prototype.overlaySize = function() {
1986 this.showOverlay(this.screenSize.width + 'x' + this.screenSize.height);
1987};
1988
rginda87b86462011-12-14 13:48:03 -08001989/**
1990 * Invoked by hterm.Terminal.Keyboard when a VT keystroke is detected.
1991 *
1992 * @param {string} string The VT string representing the keystroke.
1993 */
1994hterm.Terminal.prototype.onVTKeystroke = function(string) {
rginda9f5222b2012-03-05 11:53:28 -08001995 if (this.scrollOnKeystroke_)
rginda87b86462011-12-14 13:48:03 -08001996 this.scrollPort_.scrollRowToBottom(this.getRowCount());
1997
1998 this.io.onVTKeystroke(string);
rginda8ba33642011-12-14 12:31:31 -08001999};
2000
2001/**
2002 * React when the ScrollPort is scrolled.
2003 */
2004hterm.Terminal.prototype.onScroll_ = function() {
2005 this.scheduleSyncCursorPosition_();
2006};
2007
2008/**
rginda9846e2f2012-01-27 13:53:33 -08002009 * React when text is pasted into the scrollPort.
2010 */
2011hterm.Terminal.prototype.onPaste_ = function(e) {
2012 this.io.onVTKeystroke(e.text);
2013};
2014
2015/**
rginda8ba33642011-12-14 12:31:31 -08002016 * React when the ScrollPort is resized.
rgindac9bc5502012-01-18 11:48:44 -08002017 *
2018 * Note: This function should not directly contain code that alters the internal
2019 * state of the terminal. That kind of code belongs in realizeWidth or
2020 * realizeHeight, so that it can be executed synchronously in the case of a
2021 * programmatic width change.
rginda8ba33642011-12-14 12:31:31 -08002022 */
2023hterm.Terminal.prototype.onResize_ = function() {
rgindac9bc5502012-01-18 11:48:44 -08002024 var columnCount = Math.floor(this.scrollPort_.getScreenWidth() /
rginda35c456b2012-02-09 17:29:05 -08002025 this.scrollPort_.characterSize.width);
2026 var rowCount = Math.floor(this.scrollPort_.getScreenHeight() /
2027 this.scrollPort_.characterSize.height);
2028
2029 if (!(columnCount || rowCount)) {
2030 // We avoid these situations since they happen sometimes when the terminal
2031 // gets removed from the document, and we can't deal with that.
2032 return;
2033 }
2034
Dmitry Polukhinbb2ef712012-01-19 19:00:37 +04002035 this.realizeSize_(columnCount, rowCount);
rgindac9bc5502012-01-18 11:48:44 -08002036 this.scheduleSyncCursorPosition_();
rgindaf522ce02012-04-17 17:49:17 -07002037 this.showZoomWarning_(this.scrollPort_.characterSize.zoomFactor != 1);
rgindaf0090c92012-02-10 14:58:52 -08002038 this.overlaySize();
rginda8ba33642011-12-14 12:31:31 -08002039};
2040
2041/**
2042 * Service the cursor blink timeout.
2043 */
2044hterm.Terminal.prototype.onCursorBlink_ = function() {
rginda87b86462011-12-14 13:48:03 -08002045 if (this.cursorNode_.style.opacity == '0') {
2046 this.cursorNode_.style.opacity = '1';
rginda8ba33642011-12-14 12:31:31 -08002047 } else {
rginda87b86462011-12-14 13:48:03 -08002048 this.cursorNode_.style.opacity = '0';
rginda8ba33642011-12-14 12:31:31 -08002049 }
2050};
David Reveman8f552492012-03-28 12:18:41 -04002051
2052/**
2053 * Set the scrollbar-visible mode bit.
2054 *
2055 * If scrollbar-visible is on, the vertical scrollbar will be visible.
2056 * Otherwise it will not.
2057 *
2058 * Defaults to on.
2059 *
2060 * @param {boolean} state True to set scrollbar-visible mode, false to unset.
2061 */
2062hterm.Terminal.prototype.setScrollbarVisible = function(state) {
2063 this.scrollPort_.setScrollbarVisible(state);
2064};