]> git.proxmox.com Git - mirror_xterm.js.git/blobdiff - src/xterm.js
Little code clean up.
[mirror_xterm.js.git] / src / xterm.js
index f139bbc9ef4363ffe1b00b4702c0a227592be88b..c2785291e75666a203a3f53d8e6a7211eb343067 100644 (file)
 import { CompositionHelper } from './CompositionHelper';
 import { EventEmitter } from './EventEmitter';
 import { Viewport } from './Viewport';
-import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard';
+import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard';
 import { CircularList } from './utils/CircularList';
 import { C0 } from './EscapeSequences';
 import { InputHandler } from './InputHandler';
 import { Parser } from './Parser';
 import { Renderer } from './Renderer';
 import { Linkifier } from './Linkifier';
+import { SelectionManager } from './SelectionManager';
 import { CharMeasure } from './utils/CharMeasure';
 import * as Browser from './utils/Browser';
-import * as Keyboard from './utils/Keyboard';
+import * as Mouse from './utils/Mouse';
 import { CHARSETS } from './Charsets';
+import { getRawByteCoords } from './utils/Mouse';
 
 /**
  * Terminal Emulation References:
@@ -52,6 +54,13 @@ var WRITE_BUFFER_PAUSE_THRESHOLD = 5;
  */
 var WRITE_BATCH_SIZE = 300;
 
+/**
+ * The time between cursor blinks. This is driven by JS rather than a CSS
+ * animation due to a bug in Chromium that causes it to use excessive CPU time.
+ * See https://github.com/Microsoft/vscode/issues/22900
+ */
+var CURSOR_BLINK_INTERVAL = 600;
+
 /**
  * Terminal
  */
@@ -158,7 +167,8 @@ function Terminal(options) {
   this.queue = '';
   this.scrollTop = 0;
   this.scrollBottom = this.rows - 1;
-  this.customKeydownHandler = null;
+  this.customKeyEventHandler = null;
+  this.cursorBlinkInterval = null;
 
   // modes
   this.applicationKeypad = false;
@@ -211,7 +221,8 @@ function Terminal(options) {
   this.parser = new Parser(this.inputHandler, this);
   // Reuse renderer if the Terminal is being recreated via a Terminal.reset call.
   this.renderer = this.renderer || null;
-  this.linkifier = this.linkifier || null;;
+  this.selectionManager = this.selectionManager || null;
+  this.linkifier = this.linkifier || new Linkifier();
 
   // user input states
   this.writeBuffer = [];
@@ -240,6 +251,10 @@ function Terminal(options) {
   while (i--) {
     this.lines.push(this.blankLine());
   }
+  // Ensure the selection manager has the correct buffer
+  if (this.selectionManager) {
+    this.selectionManager.setBuffer(this.lines);
+  }
 
   this.tabs;
   this.setupStops();
@@ -423,7 +438,7 @@ Terminal.prototype.setOption = function(key, value) {
   this[key] = value;
   this.options[key] = value;
   switch (key) {
-    case 'cursorBlink': this.element.classList.toggle('xterm-cursor-blink', value); break;
+    case 'cursorBlink': this.setCursorBlinking(value); break;
     case 'cursorStyle':
       // Style 'block' applies with no class
       this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline');
@@ -433,6 +448,29 @@ Terminal.prototype.setOption = function(key, value) {
   }
 };
 
+Terminal.prototype.restartCursorBlinking = function () {
+  this.setCursorBlinking(this.options.cursorBlink);
+};
+
+Terminal.prototype.setCursorBlinking = function (enabled) {
+  this.element.classList.toggle('xterm-cursor-blink', enabled);
+  this.clearCursorBlinkingInterval();
+  if (enabled) {
+    var self = this;
+    this.cursorBlinkInterval = setInterval(function () {
+      self.element.classList.toggle('xterm-cursor-blink-on');
+    }, CURSOR_BLINK_INTERVAL);
+  }
+};
+
+Terminal.prototype.clearCursorBlinkingInterval = function () {
+  this.element.classList.remove('xterm-cursor-blink-on');
+  if (this.cursorBlinkInterval) {
+    clearInterval(this.cursorBlinkInterval);
+    this.cursorBlinkInterval = null;
+  }
+};
+
 /**
  * Binds the desired focus behavior on a given terminal object.
  *
@@ -445,6 +483,7 @@ Terminal.bindFocus = function (term) {
     }
     term.element.classList.add('focus');
     term.showCursor();
+    term.restartCursorBlinking.apply(term);
     Terminal.focus = term;
     term.emit('focus', {terminal: term});
   });
@@ -469,6 +508,7 @@ Terminal.bindBlur = function (term) {
       term.send(C0.ESC + '[O');
     }
     term.element.classList.remove('focus');
+    term.clearCursorBlinkingInterval.apply(term);
     Terminal.focus = null;
     term.emit('blur', {terminal: term});
   });
@@ -485,28 +525,43 @@ Terminal.prototype.initGlobal = function() {
   Terminal.bindBlur(this);
 
   // Bind clipboard functionality
-  on(this.element, 'copy', function (ev) {
-    copyHandler.call(this, ev, term);
-  });
-  on(this.textarea, 'paste', function (ev) {
-    pasteHandler.call(this, ev, term);
-  });
-  on(this.element, 'paste', function (ev) {
-    pasteHandler.call(this, ev, term);
+  on(this.element, 'copy', event => {
+    // If mouse events are active it means the selection manager is disabled and
+    // copy should be handled by the host program.
+    if (this.mouseEvents) {
+      return;
+    }
+    copyHandler(event, term, this.selectionManager);
   });
+  const pasteHandlerWrapper = event => pasteHandler(event, term);
+  on(this.textarea, 'paste', pasteHandlerWrapper);
+  on(this.element, 'paste', pasteHandlerWrapper);
 
-  function rightClickHandlerWrapper (ev) {
-    rightClickHandler.call(this, ev, term);
-  }
-
+  // Handle right click context menus
   if (term.browser.isFirefox) {
-    on(this.element, 'mousedown', function (ev) {
-      if (ev.button == 2) {
-        rightClickHandlerWrapper(ev);
+    // Firefox doesn't appear to fire the contextmenu event on right click
+    on(this.element, 'mousedown', event => {
+      if (event.button == 2) {
+        rightClickHandler(event, this.textarea, this.selectionManager);
       }
     });
   } else {
-    on(this.element, 'contextmenu', rightClickHandlerWrapper);
+    on(this.element, 'contextmenu', event => {
+      rightClickHandler(event, this.textarea, this.selectionManager);
+    });
+  }
+
+  // Move the textarea under the cursor when middle clicking on Linux to ensure
+  // middle click to paste selection works. This only appears to work in Chrome
+  // at the time is writing.
+  if (term.browser.isLinux) {
+    // Use auxclick event over mousedown the latter doesn't seem to work. Note
+    // that the regular click event doesn't fire for the middle mouse button.
+    on(this.element, 'auxclick', event => {
+      if (event.button === 1) {
+        moveTextAreaUnderMouseCursor(event, this.textarea, this.selectionManager);
+      }
+    });
   }
 };
 
@@ -574,8 +629,9 @@ Terminal.prototype.insertRow = function (row) {
  * Opens the terminal within an element.
  *
  * @param {HTMLElement} parent The element to create the terminal within.
+ * @param {boolean} focus Focus the terminal, after it gets instantiated in the DOM
  */
-Terminal.prototype.open = function(parent) {
+Terminal.prototype.open = function(parent, focus) {
   var self=this, i=0, div;
 
   this.parent = parent || this.parent;
@@ -594,9 +650,8 @@ Terminal.prototype.open = function(parent) {
   this.element.classList.add('terminal');
   this.element.classList.add('xterm');
   this.element.classList.add('xterm-theme-' + this.theme);
-  this.element.classList.toggle('xterm-cursor-blink', this.options.cursorBlink);
+  this.setCursorBlinking(this.options.cursorBlink);
 
-  this.element.style.height
   this.element.setAttribute('tabindex', 0);
 
   this.viewportElement = document.createElement('div');
@@ -606,13 +661,18 @@ Terminal.prototype.open = function(parent) {
   this.viewportScrollArea.classList.add('xterm-scroll-area');
   this.viewportElement.appendChild(this.viewportScrollArea);
 
+  // Create the selection container.
+  this.selectionContainer = document.createElement('div');
+  this.selectionContainer.classList.add('xterm-selection');
+  this.element.appendChild(this.selectionContainer);
+
   // Create the container that will hold the lines of the terminal and then
   // produce the lines the lines.
   this.rowContainer = document.createElement('div');
   this.rowContainer.classList.add('xterm-rows');
   this.element.appendChild(this.rowContainer);
   this.children = [];
-  this.linkifier = new Linkifier(document, this.children);
+  this.linkifier.attachToDom(document, this.children);
 
   // Create the container that will hold helpers like the textarea for
   // capturing DOM Events. Then produce the helpers.
@@ -649,12 +709,26 @@ Terminal.prototype.open = function(parent) {
 
   this.charMeasure = new CharMeasure(document, this.helperContainer);
   this.charMeasure.on('charsizechanged', function () {
-    self.updateCharSizeCSS();
+    self.updateCharSizeStyles();
   });
   this.charMeasure.measure();
 
   this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);
   this.renderer = new Renderer(this);
+  this.selectionManager = new SelectionManager(this, this.lines, this.rowContainer, this.charMeasure);
+  this.selectionManager.on('refresh', data => {
+    this.renderer.refreshSelection(data.start, data.end);
+  });
+  this.selectionManager.on('newselection', text => {
+    // If there's a new selection, put it into the textarea, focus and select it
+    // in order to register it as a selection on the OS. This event is fired
+    // only on Linux to enable middle click to paste selection.
+    this.textarea.value = text;
+    this.textarea.focus();
+    this.textarea.select();
+  });
+  this.on('scroll', () => this.selectionManager.refresh());
+  this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh());
 
   // Setup loop that draws to screen
   this.refresh(0, this.rows - 1);
@@ -663,8 +737,23 @@ Terminal.prototype.open = function(parent) {
   // need to be taken on the document.
   this.initGlobal();
 
-  // Ensure there is a Terminal.focus.
-  this.focus();
+  /**
+   * Automatic focus functionality.
+   * TODO: Default to `false` starting with xterm.js 3.0.
+   */
+  if (typeof focus == 'undefined') {
+    let message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\n';
+
+    message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 ';
+    message += 'it will default to `false`.';
+
+    console.warn(message);
+    focus = true;
+  }
+
+  if (focus) {
+    this.focus();
+  }
 
   on(this.element, 'click', function() {
     var selection = document.getSelection(),
@@ -710,8 +799,11 @@ Terminal.loadAddon = function(addon, callback) {
  * Updates the helper CSS class with any changes necessary after the terminal's
  * character width has been changed.
  */
-Terminal.prototype.updateCharSizeCSS = function() {
-  this.charSizeStyleElement.textContent = '.xterm-wide-char{width:' + (this.charMeasure.width * 2) + 'px;}';
+Terminal.prototype.updateCharSizeStyles = function() {
+  this.charSizeStyleElement.textContent =
+      `.xterm-wide-char{width:${this.charMeasure.width * 2}px;}` +
+      `.xterm-normal-char{width:${this.charMeasure.width}px;}` +
+      `.xterm-rows > div{height:${this.charMeasure.height}px;}`;
 }
 
 /**
@@ -738,7 +830,7 @@ Terminal.prototype.bindMouse = function() {
     button = getButton(ev);
 
     // get mouse coordinates
-    pos = getCoords(ev);
+    pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);
     if (!pos) return;
 
     sendEvent(button, pos);
@@ -766,7 +858,7 @@ Terminal.prototype.bindMouse = function() {
     var button = pressed
     , pos;
 
-    pos = getCoords(ev);
+    pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);
     if (!pos) return;
 
     // buttons marked as motions
@@ -941,50 +1033,6 @@ Terminal.prototype.bindMouse = function() {
     return button;
   }
 
-  // mouse coordinates measured in cols/rows
-  function getCoords(ev) {
-    var x, y, w, h, el;
-
-    // ignore browsers without pageX for now
-    if (ev.pageX == null) return;
-
-    x = ev.pageX;
-    y = ev.pageY;
-    el = self.element;
-
-    // should probably check offsetParent
-    // but this is more portable
-    while (el && el !== self.document.documentElement) {
-      x -= el.offsetLeft;
-      y -= el.offsetTop;
-      el = 'offsetParent' in el
-        ? el.offsetParent
-      : el.parentNode;
-    }
-
-    // convert to cols/rows
-    x = Math.ceil(x / self.charMeasure.width);
-    y = Math.ceil(y / self.charMeasure.height);
-
-    // be sure to avoid sending
-    // bad positions to the program
-    if (x < 0) x = 0;
-    if (x > self.cols) x = self.cols;
-    if (y < 0) y = 0;
-    if (y > self.rows) y = self.rows;
-
-    // xterm sends raw bytes and
-    // starts at 32 (SP) for each.
-    x += 32;
-    y += 32;
-
-    return {
-      x: x,
-      y: y,
-      type: 'wheel'
-    };
-  }
-
   on(el, 'mousedown', function(ev) {
     if (!self.mouseEvents) return;
 
@@ -1078,7 +1126,7 @@ Terminal.prototype.queueLinkification = function(start, end) {
       this.linkifier.linkifyRow(i);
     }
   }
-}
+};
 
 /**
  * Display the cursor element
@@ -1092,8 +1140,10 @@ Terminal.prototype.showCursor = function() {
 
 /**
  * Scroll the terminal down 1 row, creating a blank line.
+ * @param {boolean} isWrapped Whether the new line is wrapped from the previous
+ * line.
  */
-Terminal.prototype.scroll = function() {
+Terminal.prototype.scroll = function(isWrapped) {
   var row;
 
   // Make room for the new row in lines
@@ -1120,10 +1170,10 @@ Terminal.prototype.scroll = function() {
 
   if (row === this.lines.length) {
     // Optimization: pushing is faster than splicing when they amount to the same behavior
-    this.lines.push(this.blankLine());
+    this.lines.push(this.blankLine(undefined, isWrapped));
   } else {
     // add our new line
-    this.lines.splice(row, 0, this.blankLine());
+    this.lines.splice(row, 0, this.blankLine(undefined, isWrapped));
   }
 
   if (this.scrollTop !== 0) {
@@ -1158,6 +1208,9 @@ Terminal.prototype.scroll = function() {
  */
 Terminal.prototype.scrollDisp = function(disp, suppressScrollEvent) {
   if (disp < 0) {
+    if (this.ydisp === 0) {
+      return;
+    }
     this.userScrolling = true;
   } else if (disp + this.ydisp >= this.ybase) {
     this.userScrolling = false;
@@ -1184,25 +1237,25 @@ Terminal.prototype.scrollDisp = function(disp, suppressScrollEvent) {
  */
 Terminal.prototype.scrollPages = function(pageCount) {
   this.scrollDisp(pageCount * (this.rows - 1));
-}
+};
 
 /**
  * Scrolls the display of the terminal to the top.
  */
 Terminal.prototype.scrollToTop = function() {
   this.scrollDisp(-this.ydisp);
-}
+};
 
 /**
  * Scrolls the display of the terminal to the bottom.
  */
 Terminal.prototype.scrollToBottom = function() {
   this.scrollDisp(this.ybase - this.ydisp);
-}
+};
 
 /**
  * Writes text to the terminal.
- * @param {string} text The text to write to the terminal.
+ * @param {string} data The text to write to the terminal.
  */
 Terminal.prototype.write = function(data) {
   this.writeBuffer.push(data);
@@ -1226,7 +1279,7 @@ Terminal.prototype.write = function(data) {
       self.innerWrite();
     });
   }
-}
+};
 
 Terminal.prototype.innerWrite = function() {
   var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);
@@ -1244,7 +1297,13 @@ Terminal.prototype.innerWrite = function() {
     this.refreshStart = this.y;
     this.refreshEnd = this.y;
 
-    this.parser.parse(data);
+    // HACK: Set the parser state based on it's state at the time of return.
+    // This works around the bug #662 which saw the parser state reset in the
+    // middle of parsing escape sequence in two chunks. For some reason the
+    // state of the parser resets to 0 after exiting parser.parse. This change
+    // just sets the state back based on the correct return statement.
+    var state = this.parser.parse(data);
+    this.parser.setState(state);
 
     this.updateRange(this.y);
     this.refresh(this.refreshStart, this.refreshEnd);
@@ -1262,29 +1321,41 @@ Terminal.prototype.innerWrite = function() {
 
 /**
  * Writes text to the terminal, followed by a break line character (\n).
- * @param {string} text The text to write to the terminal.
+ * @param {string} data The text to write to the terminal.
  */
 Terminal.prototype.writeln = function(data) {
   this.write(data + '\r\n');
 };
 
 /**
- * Attaches a custom keydown handler which is run before keys are processed, giving consumers of
- * xterm.js ultimate control as to what keys should be processed by the terminal and what keys
- * should not.
+ * DEPRECATED: only for backward compatibility. Please use attachCustomKeyEventHandler() instead.
  * @param {function} customKeydownHandler The custom KeyboardEvent handler to attach. This is a
  *   function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent
  *   the default action. The function returns whether the event should be processed by xterm.js.
  */
 Terminal.prototype.attachCustomKeydownHandler = function(customKeydownHandler) {
-  this.customKeydownHandler = customKeydownHandler;
-}
+  let message = 'attachCustomKeydownHandler() is DEPRECATED and will be removed soon. Please use attachCustomKeyEventHandler() instead.';
+  console.warn(message);
+  this.attachCustomKeyEventHandler(customKeydownHandler);
+};
+
+/**
+ * Attaches a custom key event handler which is run before keys are processed, giving consumers of
+ * xterm.js ultimate control as to what keys should be processed by the terminal and what keys
+ * should not.
+ * @param {function} customKeyEventHandler The custom KeyboardEvent handler to attach. This is a
+ *   function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent
+ *   the default action. The function returns whether the event should be processed by xterm.js.
+ */
+Terminal.prototype.attachCustomKeyEventHandler = function(customKeyEventHandler) {
+  this.customKeyEventHandler = customKeyEventHandler;
+};
 
 /**
  * Attaches a http(s) link handler, forcing web links to behave differently to
  * regular <a> tags. This will trigger a refresh as links potentially need to be
  * reconstructed. Calling this with null will remove the handler.
- * @param {LinkHandler} handler The handler callback function.
+ * @param {handler} handler The handler callback function.
  */
 Terminal.prototype.setHypertextLinkHandler = function(handler) {
   if (!this.linkifier) {
@@ -1293,12 +1364,12 @@ Terminal.prototype.setHypertextLinkHandler = function(handler) {
   this.linkifier.setHypertextLinkHandler(handler);
   // Refresh to force links to refresh
   this.refresh(0, this.rows - 1);
-}
+};
 
 /**
  * Attaches a validation callback for hypertext links. This is useful to use
  * validation logic or to do something with the link's element and url.
- * @param {LinkMatcherValidationCallback} callback The callback to use, this can
+ * @param {LinkMatcherValidationCallback} handler The callback to use, this can
  * be cleared with null.
  */
 Terminal.prototype.setHypertextValidationCallback = function(handler) {
@@ -1308,7 +1379,7 @@ Terminal.prototype.setHypertextValidationCallback = function(handler) {
   this.linkifier.setHypertextValidationCallback(handler);
   // Refresh to force links to refresh
   this.refresh(0, this.rows - 1);
-}
+};
 
 /**
    * Registers a link matcher, allowing custom link patterns to be matched and
@@ -1316,7 +1387,7 @@ Terminal.prototype.setHypertextValidationCallback = function(handler) {
    * @param {RegExp} regex The regular expression to search for, specifically
    * this searches the textContent of the rows. You will want to use \s to match
    * a space ' ' character for example.
-   * @param {LinkHandler} handler The callback when the link is called.
+   * @param {handler} handler The callback when the link is called.
    * @param {LinkMatcherOptions} [options] Options for the link matcher.
    * @return {number} The ID of the new matcher, this can be used to deregister.
  */
@@ -1326,7 +1397,7 @@ Terminal.prototype.registerLinkMatcher = function(regex, handler, options) {
     this.refresh(0, this.rows - 1);
     return matcherId;
   }
-}
+};
 
 /**
  * Deregisters a link matcher if it has been registered.
@@ -1338,7 +1409,36 @@ Terminal.prototype.deregisterLinkMatcher = function(matcherId) {
       this.refresh(0, this.rows - 1);
     }
   }
-}
+};
+
+/**
+ * Gets whether the terminal has an active selection.
+ */
+Terminal.prototype.hasSelection = function() {
+  return this.selectionManager.hasSelection;
+};
+
+/**
+ * Gets the terminal's current selection, this is useful for implementing copy
+ * behavior outside of xterm.js.
+ */
+Terminal.prototype.getSelection = function() {
+  return this.selectionManager.selectionText;
+};
+
+/**
+ * Clears the current terminal selection.
+ */
+Terminal.prototype.clearSelection = function() {
+  this.selectionManager.clearSelection();
+};
+
+/**
+ * Selects all text within the terminal.
+ */
+Terminal.prototype.selectAll = function() {
+  this.selectionManager.selectAll();
+};
 
 /**
  * Handle a keydown event
@@ -1347,10 +1447,12 @@ Terminal.prototype.deregisterLinkMatcher = function(matcherId) {
  * @param {KeyboardEvent} ev The keydown event to be handled.
  */
 Terminal.prototype.keyDown = function(ev) {
-  if (this.customKeydownHandler && this.customKeydownHandler(ev) === false) {
+  if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {
     return false;
   }
 
+  this.restartCursorBlinking();
+
   if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {
     if (this.ybase !== this.ydisp) {
       this.scrollToBottom();
@@ -1669,6 +1771,10 @@ Terminal.prototype.evaluateKeyEscapeSequence = function(ev) {
         } else if (ev.keyCode >= 48 && ev.keyCode <= 57) {
           result.key = C0.ESC + (ev.keyCode - 48);
         }
+      } else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) {
+        if (ev.keyCode === 65) { // cmd + a
+          this.selectAll();
+        }
       }
       break;
   }
@@ -1706,6 +1812,10 @@ Terminal.prototype.setgCharset = function(g, charset) {
 Terminal.prototype.keyPress = function(ev) {
   var key;
 
+  if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {
+    return false;
+  }
+
   this.cancel(ev);
 
   if (ev.charCode) {
@@ -1731,7 +1841,7 @@ Terminal.prototype.keyPress = function(ev) {
   this.showCursor();
   this.handler(key);
 
-  return false;
+  return true;
 };
 
 /**
@@ -1820,14 +1930,8 @@ Terminal.prototype.resize = function(x, y) {
         this.lines.get(i).push(ch);
       }
     }
-  } else { // (j > x)
-    i = this.lines.length;
-    while (i--) {
-      while (this.lines.get(i).length > x) {
-        this.lines.get(i).pop();
-      }
-    }
   }
+
   this.cols = x;
   this.setupStops(this.cols);
 
@@ -1843,7 +1947,7 @@ Terminal.prototype.resize = function(x, y) {
           // There is room above the buffer and there are no empty elements below the line,
           // scroll up
           this.ybase--;
-          addToY++
+          addToY++;
           if (this.ydisp > 0) {
             // Viewport is at the top of the buffer, must increase downwards
             this.ydisp--;
@@ -2044,8 +2148,9 @@ Terminal.prototype.eraseLine = function(y) {
 /**
  * Return the data array of a blank line
  * @param {number} cur First bunch of data for each "blank" character.
+ * @param {boolean} isWrapped Whether the new line is wrapped from the previous line.
  */
-Terminal.prototype.blankLine = function(cur) {
+Terminal.prototype.blankLine = function(cur, isWrapped) {
   var attr = cur
   ? this.eraseAttr()
   : this.defAttr;
@@ -2054,6 +2159,12 @@ Terminal.prototype.blankLine = function(cur) {
   , line = []
   , i = 0;
 
+  // TODO: It is not ideal that this is a property on an array, a buffer line
+  // class should be added that will hold this data and other useful functions.
+  if (isWrapped) {
+    line.isWrapped = isWrapped;
+  }
+
   for (; i < this.cols; i++) {
     line[i] = ch;
   }
@@ -2163,9 +2274,11 @@ Terminal.prototype.reverseIndex = function() {
 Terminal.prototype.reset = function() {
   this.options.rows = this.rows;
   this.options.cols = this.cols;
-  var customKeydownHandler = this.customKeydownHandler;
+  var customKeyEventHandler = this.customKeyEventHandler;
+  var cursorBlinkInterval = this.cursorBlinkInterval;
   Terminal.call(this, this.options);
-  this.customKeydownHandler = customKeydownHandler;
+  this.customKeyEventHandler = customKeyEventHandler;
+  this.cursorBlinkInterval = cursorBlinkInterval;
   this.refresh(0, this.rows - 1);
   this.viewport.syncScrollArea();
 };