]> git.proxmox.com Git - mirror_xterm.js.git/blobdiff - src/xterm.js
Merge pull request #926 from ficristo/search-fix
[mirror_xterm.js.git] / src / xterm.js
index 8f2d64e3f747e0b8164b440895fe5e66b66df310..38e7b9c6eb25e547f7276246653f9a18d0357cd5 100644 (file)
  * @license MIT
  */
 
+import { BufferSet } from './BufferSet';
 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';
@@ -21,12 +22,12 @@ import { Parser } from './Parser';
 import { Renderer } from './Renderer';
 import { Linkifier } from './Linkifier';
 import { SelectionManager } from './SelectionManager';
-import { SearchHelper } from './SearchHelper';
 import { CharMeasure } from './utils/CharMeasure';
 import * as Browser from './utils/Browser';
 import * as Mouse from './utils/Mouse';
 import { CHARSETS } from './Charsets';
 import { getRawByteCoords } from './utils/Mouse';
+import { translateBufferLineToString } from './utils/BufferLine';
 
 /**
  * Terminal Emulation References:
@@ -141,34 +142,11 @@ function Terminal(options) {
     this.on('data', options.handler);
   }
 
-  /**
-   * The scroll position of the y cursor, ie. ybase + y = the y position within the entire
-   * buffer
-   */
-  this.ybase = 0;
-
-  /**
-   * The scroll position of the viewport
-   */
-  this.ydisp = 0;
-
-  /**
-   * The cursor's x position after ybase
-   */
-  this.x = 0;
-
-  /**
-   * The cursor's y position after ybase
-   */
-  this.y = 0;
-
   this.cursorState = 0;
   this.cursorHidden = false;
   this.convertEol;
   this.queue = '';
-  this.scrollTop = 0;
-  this.scrollBottom = this.rows - 1;
-  this.customKeydownHandler = null;
+  this.customKeyEventHandler = null;
   this.cursorBlinkInterval = null;
 
   // modes
@@ -177,7 +155,6 @@ function Terminal(options) {
   this.originMode = false;
   this.insertMode = false;
   this.wraparoundMode = true; // defaults: xterm - true, vt100 - false
-  this.normal = null;
 
   // charset
   this.charset = null;
@@ -224,7 +201,6 @@ function Terminal(options) {
   this.renderer = this.renderer || null;
   this.selectionManager = this.selectionManager || null;
   this.linkifier = this.linkifier || new Linkifier();
-  this.searchHelper = this.searchHelper || null;
 
   // user input states
   this.writeBuffer = [];
@@ -244,21 +220,18 @@ function Terminal(options) {
   // leftover surrogate high from previous write invocation
   this.surrogate_high = '';
 
-  /**
-   * An array of all lines in the entire buffer, including the prompt. The lines are array of
-   * characters which are 2-length arrays where [0] is an attribute and [1] is the character.
-   */
-  this.lines = new CircularList(this.scrollback);
-  var i = this.rows;
-  while (i--) {
-    this.lines.push(this.blankLine());
-  }
+  // Create the terminal's buffers and set the current buffer
+  this.buffers = new BufferSet(this);
+  this.buffer = this.buffers.active;  // Convenience shortcut;
+  this.buffers.on('activate', function (buffer) {
+    this._terminal.buffer = buffer;
+  });
+
   // Ensure the selection manager has the correct buffer
   if (this.selectionManager) {
-    this.selectionManager.setBuffer(this.lines);
+    this.selectionManager.setBuffer(this.buffer.lines);
   }
 
-  this.tabs;
   this.setupStops();
 
   // Store if user went browsing history in scrollback
@@ -398,7 +371,7 @@ Terminal.prototype.focus = function() {
  * Retrieves an option's value from the terminal.
  * @param {string} key The option key.
  */
-Terminal.prototype.getOption = function(key, value) {
+Terminal.prototype.getOption = function(key) {
   if (!(key in Terminal.defaults)) {
     throw new Error('No option with key "' + key + '"');
   }
@@ -421,18 +394,27 @@ Terminal.prototype.setOption = function(key, value) {
   }
   switch (key) {
     case 'scrollback':
+      if (value < this.rows) {
+        let msg = 'Setting the scrollback value less than the number of rows ';
+
+        msg += `(${this.rows}) is not allowed.`;
+
+        console.warn(msg);
+        return false;
+      }
+
       if (this.options[key] !== value) {
-        if (this.lines.length > value) {
-          const amountToTrim = this.lines.length - value;
-          const needsRefresh = (this.ydisp - amountToTrim < 0);
-          this.lines.trimStart(amountToTrim);
-          this.ybase = Math.max(this.ybase - amountToTrim, 0);
-          this.ydisp = Math.max(this.ydisp - amountToTrim, 0);
+        if (this.buffer.lines.length > value) {
+          const amountToTrim = this.buffer.lines.length - value;
+          const needsRefresh = (this.buffer.ydisp - amountToTrim < 0);
+          this.buffer.lines.trimStart(amountToTrim);
+          this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0);
+          this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0);
           if (needsRefresh) {
             this.refresh(0, this.rows - 1);
           }
         }
-        this.lines.maxLength = value;
+        this.buffer.lines.maxLength = value;
         this.viewport.syncScrollArea();
       }
       break;
@@ -442,7 +424,7 @@ Terminal.prototype.setOption = function(key, value) {
   switch (key) {
     case 'cursorBlink': this.setCursorBlinking(value); break;
     case 'cursorStyle':
-      // Style 'block' applies with no class
+      this.element.classList.toggle(`xterm-cursor-style-block`, value === 'block');
       this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline');
       this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar');
       break;
@@ -505,7 +487,7 @@ Terminal.prototype.blur = function() {
  */
 Terminal.bindBlur = function (term) {
   on(term.textarea, 'blur', function (ev) {
-    term.refresh(term.y, term.y);
+    term.refresh(term.buffer.y, term.buffer.y);
     if (term.sendFocus) {
       term.send(C0.ESC + '[O');
     }
@@ -530,7 +512,7 @@ Terminal.prototype.initGlobal = function() {
   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) {
+    if (!term.hasSelection()) {
       return;
     }
     copyHandler(event, term, this.selectionManager);
@@ -539,9 +521,11 @@ Terminal.prototype.initGlobal = function() {
   on(this.textarea, 'paste', pasteHandlerWrapper);
   on(this.element, 'paste', pasteHandlerWrapper);
 
+  // Handle right click context menus
   if (term.browser.isFirefox) {
+    // Firefox doesn't appear to fire the contextmenu event on right click
     on(this.element, 'mousedown', event => {
-      if (ev.button == 2) {
+      if (event.button == 2) {
         rightClickHandler(event, this.textarea, this.selectionManager);
       }
     });
@@ -550,6 +534,19 @@ Terminal.prototype.initGlobal = function() {
       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);
+      }
+    });
+  }
 };
 
 /**
@@ -637,9 +634,9 @@ Terminal.prototype.open = function(parent, focus) {
   this.element.classList.add('terminal');
   this.element.classList.add('xterm');
   this.element.classList.add('xterm-theme-' + this.theme);
+  this.element.classList.add(`xterm-cursor-style-${this.options.cursorStyle}`);
   this.setCursorBlinking(this.options.cursorBlink);
 
-  this.element.style.height;
   this.element.setAttribute('tabindex', 0);
 
   this.viewportElement = document.createElement('div');
@@ -649,8 +646,7 @@ Terminal.prototype.open = function(parent, focus) {
   this.viewportScrollArea.classList.add('xterm-scroll-area');
   this.viewportElement.appendChild(this.viewportScrollArea);
 
-  // Create the selection container. This needs to be added before the
-  // rowContainer as the selection must be below the text.
+  // Create the selection container.
   this.selectionContainer = document.createElement('div');
   this.selectionContainer.classList.add('xterm-selection');
   this.element.appendChild(this.selectionContainer);
@@ -704,11 +700,22 @@ Terminal.prototype.open = function(parent, focus) {
 
   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 = new SelectionManager(
+    this, this.buffer.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());
-  this.searchHelper = new SearchHelper(this);;
 
   // Setup loop that draws to screen
   this.refresh(0, this.rows - 1);
@@ -735,15 +742,6 @@ Terminal.prototype.open = function(parent, focus) {
     this.focus();
   }
 
-  on(this.element, 'click', function() {
-    var selection = document.getSelection(),
-        collapsed = selection.isCollapsed,
-        isRange = typeof collapsed == 'boolean' ? !collapsed : selection.type == 'Range';
-    if (!isRange) {
-      self.focus();
-    }
-  });
-
   // Listen for mouse events and translate
   // them into terminal mouse protocols.
   this.bindMouse();
@@ -782,7 +780,8 @@ Terminal.loadAddon = function(addon, callback) {
 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-normal-char{width:${this.charMeasure.width}px;}` +
+      `.xterm-rows > div{height:${this.charMeasure.height}px;}`;
 }
 
 /**
@@ -1013,14 +1012,17 @@ Terminal.prototype.bindMouse = function() {
   }
 
   on(el, 'mousedown', function(ev) {
+
+    // Prevent the focus on the textarea from getting lost
+    // and make sure we get focused on mousedown
+    ev.preventDefault();
+    self.focus();
+
     if (!self.mouseEvents) return;
 
     // send the button
     sendButton(ev);
 
-    // ensure focus
-    self.focus();
-
     // fix for odd bug
     //if (self.vt200Mouse && !self.normalMouse) {
     if (self.vt200Mouse) {
@@ -1065,6 +1067,18 @@ Terminal.prototype.bindMouse = function() {
     self.viewport.onWheel(ev);
     return self.cancel(ev);
   });
+
+  on(el, 'touchstart', function(ev) {
+    if (self.mouseEvents) return;
+    self.viewport.onTouchStart(ev);
+    return self.cancel(ev);
+  });
+
+  on(el, 'touchmove', function(ev) {
+    if (self.mouseEvents) return;
+    self.viewport.onTouchMove(ev);
+    return self.cancel(ev);
+  });
 };
 
 /**
@@ -1105,7 +1119,7 @@ Terminal.prototype.queueLinkification = function(start, end) {
       this.linkifier.linkifyRow(i);
     }
   }
-}
+};
 
 /**
  * Display the cursor element
@@ -1113,59 +1127,61 @@ Terminal.prototype.queueLinkification = function(start, end) {
 Terminal.prototype.showCursor = function() {
   if (!this.cursorState) {
     this.cursorState = 1;
-    this.refresh(this.y, this.y);
+    this.refresh(this.buffer.y, this.buffer.y);
   }
 };
 
 /**
  * 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
-  if (this.lines.length === this.lines.maxLength) {
-    this.lines.trimStart(1);
-    this.ybase--;
-    if (this.ydisp !== 0) {
-      this.ydisp--;
+  if (this.buffer.lines.length === this.buffer.lines.maxLength) {
+    this.buffer.lines.trimStart(1);
+    this.buffer.ybase--;
+    if (this.buffer.ydisp !== 0) {
+      this.buffer.ydisp--;
     }
   }
 
-  this.ybase++;
+  this.buffer.ybase++;
 
   // TODO: Why is this done twice?
   if (!this.userScrolling) {
-    this.ydisp = this.ybase;
+    this.buffer.ydisp = this.buffer.ybase;
   }
 
   // last line
-  row = this.ybase + this.rows - 1;
+  row = this.buffer.ybase + this.rows - 1;
 
   // subtract the bottom scroll region
-  row -= this.rows - 1 - this.scrollBottom;
+  row -= this.rows - 1 - this.buffer.scrollBottom;
 
-  if (row === this.lines.length) {
+  if (row === this.buffer.lines.length) {
     // Optimization: pushing is faster than splicing when they amount to the same behavior
-    this.lines.push(this.blankLine());
+    this.buffer.lines.push(this.blankLine(undefined, isWrapped));
   } else {
     // add our new line
-    this.lines.splice(row, 0, this.blankLine());
+    this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped));
   }
 
-  if (this.scrollTop !== 0) {
-    if (this.ybase !== 0) {
-      this.ybase--;
+  if (this.buffer.scrollTop !== 0) {
+    if (this.buffer.ybase !== 0) {
+      this.buffer.ybase--;
       if (!this.userScrolling) {
-        this.ydisp = this.ybase;
+        this.buffer.ydisp = this.buffer.ybase;
       }
     }
-    this.lines.splice(this.ybase + this.scrollTop, 1);
+    this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1);
   }
 
   // this.maxRange();
-  this.updateRange(this.scrollTop);
-  this.updateRange(this.scrollBottom);
+  this.updateRange(this.buffer.scrollTop);
+  this.updateRange(this.buffer.scrollBottom);
 
   /**
    * This event is emitted whenever the terminal is scrolled.
@@ -1173,7 +1189,7 @@ Terminal.prototype.scroll = function() {
    *
    * @event scroll
    */
-  this.emit('scroll', this.ydisp);
+  this.emit('scroll', this.buffer.ydisp);
 };
 
 /**
@@ -1185,24 +1201,24 @@ Terminal.prototype.scroll = function() {
  */
 Terminal.prototype.scrollDisp = function(disp, suppressScrollEvent) {
   if (disp < 0) {
-    if (this.ydisp === 0) {
+    if (this.buffer.ydisp === 0) {
       return;
     }
     this.userScrolling = true;
-  } else if (disp + this.ydisp >= this.ybase) {
+  } else if (disp + this.buffer.ydisp >= this.buffer.ybase) {
     this.userScrolling = false;
   }
 
-  this.ydisp += disp;
+  const oldYdisp = this.buffer.ydisp;
+  this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0);
 
-  if (this.ydisp > this.ybase) {
-    this.ydisp = this.ybase;
-  } else if (this.ydisp < 0) {
-    this.ydisp = 0;
+  // No change occurred, don't trigger scroll/refresh
+  if (oldYdisp === this.buffer.ydisp) {
+    return;
   }
 
   if (!suppressScrollEvent) {
-    this.emit('scroll', this.ydisp);
+    this.emit('scroll', this.buffer.ydisp);
   }
 
   this.refresh(0, this.rows - 1);
@@ -1214,25 +1230,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);
-}
+  this.scrollDisp(-this.buffer.ydisp);
+};
 
 /**
  * Scrolls the display of the terminal to the bottom.
  */
 Terminal.prototype.scrollToBottom = function() {
-  this.scrollDisp(this.ybase - this.ydisp);
-}
+  this.scrollDisp(this.buffer.ybase - this.buffer.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);
@@ -1256,7 +1272,7 @@ Terminal.prototype.write = function(data) {
       self.innerWrite();
     });
   }
-}
+};
 
 Terminal.prototype.innerWrite = function() {
   var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);
@@ -1271,8 +1287,8 @@ Terminal.prototype.innerWrite = function() {
       this.xoffSentToCatchUp = false;
     }
 
-    this.refreshStart = this.y;
-    this.refreshEnd = this.y;
+    this.refreshStart = this.buffer.y;
+    this.refreshEnd = this.buffer.y;
 
     // 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
@@ -1282,7 +1298,7 @@ Terminal.prototype.innerWrite = function() {
     var state = this.parser.parse(data);
     this.parser.setState(state);
 
-    this.updateRange(this.y);
+    this.updateRange(this.buffer.y);
     this.refresh(this.refreshStart, this.refreshEnd);
   }
   if (this.writeBuffer.length > 0) {
@@ -1298,29 +1314,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 {LinkMatcherHandler} handler The handler callback function.
  */
 Terminal.prototype.setHypertextLinkHandler = function(handler) {
   if (!this.linkifier) {
@@ -1329,7 +1357,7 @@ 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
@@ -1337,14 +1365,14 @@ Terminal.prototype.setHypertextLinkHandler = function(handler) {
  * @param {LinkMatcherValidationCallback} callback The callback to use, this can
  * be cleared with null.
  */
-Terminal.prototype.setHypertextValidationCallback = function(handler) {
+Terminal.prototype.setHypertextValidationCallback = function(callback) {
   if (!this.linkifier) {
     throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called');
   }
-  this.linkifier.setHypertextValidationCallback(handler);
+  this.linkifier.setHypertextValidationCallback(callback);
   // Refresh to force links to refresh
   this.refresh(0, this.rows - 1);
-}
+};
 
 /**
    * Registers a link matcher, allowing custom link patterns to be matched and
@@ -1352,7 +1380,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 {LinkMatcherHandler} 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.
  */
@@ -1362,7 +1390,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.
@@ -1374,56 +1402,40 @@ 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;
-}
+  return this.selectionManager ? this.selectionManager.hasSelection : false;
+};
 
 /**
  * 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;
-}
+  return this.selectionManager ? this.selectionManager.selectionText : '';
+};
 
 /**
  * Clears the current terminal selection.
  */
 Terminal.prototype.clearSelection = function() {
-  this.selectionManager.clearSelection();
-}
+  if (this.selectionManager) {
+    this.selectionManager.clearSelection();
+  }
+};
 
 /**
  * Selects all text within the terminal.
  */
 Terminal.prototype.selectAll = function() {
-  this.selectionManager.selectAll();
-}
-
-/**
- * Find the next instance of the term, then scroll to and select it. If it
- * doesn't exist, do nothing.
- * @param term The term to search for.
- * @return Whether a result was found.
- */
-Terminal.prototype.findNext = function(term) {
-  return this.searchHelper.findNext(term);
-}
-
-/**
- * Find the previous instance of the term, then scroll to and select it. If it
- * doesn't exist, do nothing.
- * @param term The term to search for.
- * @return Whether a result was found.
- */
-Terminal.prototype.findPrevious = function(term) {
-  return this.searchHelper.findPrevious(term);
-}
+  if (this.selectionManager) {
+    this.selectionManager.selectAll();
+  }
+};
 
 /**
  * Handle a keydown event
@@ -1432,14 +1444,14 @@ Terminal.prototype.findPrevious = function(term) {
  * @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) {
+    if (this.buffer.ybase !== this.buffer.ydisp) {
       this.scrollToBottom();
     }
     return false;
@@ -1797,6 +1809,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) {
@@ -1822,7 +1838,7 @@ Terminal.prototype.keyPress = function(ev) {
   this.showCursor();
   this.handler(key);
 
-  return false;
+  return true;
 };
 
 /**
@@ -1887,6 +1903,10 @@ Terminal.prototype.resize = function(x, y) {
     return;
   }
 
+  if (y > this.getOption('scrollback')) {
+    this.setOption('scrollback', y)
+  }
+
   var line
   , el
   , i
@@ -1895,96 +1915,36 @@ Terminal.prototype.resize = function(x, y) {
   , addToY;
 
   if (x === this.cols && y === this.rows) {
+    // Check if we still need to measure the char size (fixes #785).
+    if (!this.charMeasure.width || !this.charMeasure.height) {
+      this.charMeasure.measure();
+    }
     return;
   }
 
   if (x < 1) x = 1;
   if (y < 1) y = 1;
 
-  // resize cols
-  j = this.cols;
-  if (j < x) {
-    ch = [this.defAttr, ' ', 1]; // does xterm use the default attr?
-    i = this.lines.length;
-    while (i--) {
-      while (this.lines.get(i).length < x) {
-        this.lines.get(i).push(ch);
-      }
-    }
-  }
+  this.buffers.resize(x, y);
 
-  this.cols = x;
-  this.setupStops(this.cols);
-
-  // resize rows
-  j = this.rows;
-  addToY = 0;
-  if (j < y) {
-    el = this.element;
-    while (j++ < y) {
-      // y is rows, not this.y
-      if (this.lines.length < y + this.ybase) {
-        if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
-          // There is room above the buffer and there are no empty elements below the line,
-          // scroll up
-          this.ybase--;
-          addToY++
-          if (this.ydisp > 0) {
-            // Viewport is at the top of the buffer, must increase downwards
-            this.ydisp--;
-          }
-        } else {
-          // Add a blank line if there is no buffer left at the top to scroll to, or if there
-          // are blank lines after the cursor
-          this.lines.push(this.blankLine());
-        }
-      }
-      if (this.children.length < y) {
-        this.insertRow();
-      }
-    }
-  } else { // (j > y)
-    while (j-- > y) {
-      if (this.lines.length > y + this.ybase) {
-        if (this.lines.length > this.ybase + this.y + 1) {
-          // The line is a blank line below the cursor, remove it
-          this.lines.pop();
-        } else {
-          // The line is the cursor, scroll down
-          this.ybase++;
-          this.ydisp++;
-        }
-      }
-      if (this.children.length > y) {
-        el = this.children.shift();
-        if (!el) continue;
-        el.parentNode.removeChild(el);
-      }
-    }
-  }
-  this.rows = y;
-
-  // Make sure that the cursor stays on screen
-  if (this.y >= y) {
-    this.y = y - 1;
-  }
-  if (addToY) {
-    this.y += addToY;
+  // Adjust rows in the DOM to accurately reflect the new dimensions
+  while (this.children.length < y) {
+    this.insertRow();
   }
-
-  if (this.x >= x) {
-    this.x = x - 1;
+  while (this.children.length > y) {
+    el = this.children.shift();
+    if (!el) continue;
+    el.parentNode.removeChild(el);
   }
 
-  this.scrollTop = 0;
-  this.scrollBottom = y - 1;
+  this.cols = x;
+  this.rows = y;
+  this.setupStops(this.cols);
 
   this.charMeasure.measure();
 
   this.refresh(0, this.rows - 1);
 
-  this.normal = null;
-
   this.geometry = [this.cols, this.rows];
   this.emit('resize', {terminal: this, cols: x, rows: y});
 };
@@ -2020,16 +1980,16 @@ Terminal.prototype.maxRange = function() {
  */
 Terminal.prototype.setupStops = function(i) {
   if (i != null) {
-    if (!this.tabs[i]) {
+    if (!this.buffer.tabs[i]) {
       i = this.prevStop(i);
     }
   } else {
-    this.tabs = {};
+    this.buffer.tabs = {};
     i = 0;
   }
 
   for (; i < this.cols; i += this.getOption('tabStopWidth')) {
-    this.tabs[i] = true;
+    this.buffer.tabs[i] = true;
   }
 };
 
@@ -2039,8 +1999,8 @@ Terminal.prototype.setupStops = function(i) {
  * @param {number} x The position to move the cursor to the previous tab stop.
  */
 Terminal.prototype.prevStop = function(x) {
-  if (x == null) x = this.x;
-  while (!this.tabs[--x] && x > 0);
+  if (x == null) x = this.buffer.x;
+  while (!this.buffer.tabs[--x] && x > 0);
   return x >= this.cols
     ? this.cols - 1
   : x < 0 ? 0 : x;
@@ -2052,8 +2012,8 @@ Terminal.prototype.prevStop = function(x) {
  * @param {number} x The position to move the cursor one tab stop forward.
  */
 Terminal.prototype.nextStop = function(x) {
-  if (x == null) x = this.x;
-  while (!this.tabs[++x] && x < this.cols);
+  if (x == null) x = this.buffer.x;
+  while (!this.buffer.tabs[++x] && x < this.cols);
   return x >= this.cols
     ? this.cols - 1
   : x < 0 ? 0 : x;
@@ -2066,7 +2026,7 @@ Terminal.prototype.nextStop = function(x) {
  * @param {number} y The line in which to operate.
  */
 Terminal.prototype.eraseRight = function(x, y) {
-  var line = this.lines.get(this.ybase + y);
+  var line = this.buffer.lines.get(this.buffer.ybase + y);
   if (!line) {
     return;
   }
@@ -2085,7 +2045,7 @@ Terminal.prototype.eraseRight = function(x, y) {
  * @param {number} y The line in which to operate.
  */
 Terminal.prototype.eraseLeft = function(x, y) {
-  var line = this.lines.get(this.ybase + y);
+  var line = this.buffer.lines.get(this.buffer.ybase + y);
   if (!line) {
     return;
   }
@@ -2101,20 +2061,20 @@ Terminal.prototype.eraseLeft = function(x, y) {
  * Clears the entire buffer, making the prompt line the new first line.
  */
 Terminal.prototype.clear = function() {
-  if (this.ybase === 0 && this.y === 0) {
+  if (this.buffer.ybase === 0 && this.buffer.y === 0) {
     // Don't clear if it's already clear
     return;
   }
-  this.lines.set(0, this.lines.get(this.ybase + this.y));
-  this.lines.length = 1;
-  this.ydisp = 0;
-  this.ybase = 0;
-  this.y = 0;
+  this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y));
+  this.buffer.lines.length = 1;
+  this.buffer.ydisp = 0;
+  this.buffer.ybase = 0;
+  this.buffer.y = 0;
   for (var i = 1; i < this.rows; i++) {
-    this.lines.push(this.blankLine());
+    this.buffer.lines.push(this.blankLine());
   }
   this.refresh(0, this.rows - 1);
-  this.emit('scroll', this.ydisp);
+  this.emit('scroll', this.buffer.ydisp);
 };
 
 /**
@@ -2129,8 +2089,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, cols) {
   var attr = cur
   ? this.eraseAttr()
   : this.defAttr;
@@ -2139,7 +2100,14 @@ Terminal.prototype.blankLine = function(cur) {
   , line = []
   , i = 0;
 
-  for (; i < this.cols; i++) {
+  // 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;
+  }
+
+  cols = cols || this.cols;
+  for (; i < cols; i++) {
     line[i] = ch;
   }
 
@@ -2159,7 +2127,7 @@ Terminal.prototype.ch = function(cur) {
 
 
 /**
- * Evaluate if the current erminal is the given argument.
+ * Evaluate if the current terminal is the given argument.
  * @param {object} term The terminal to evaluate
  */
 Terminal.prototype.is = function(term) {
@@ -2178,8 +2146,13 @@ Terminal.prototype.handler = function(data) {
     return;
   }
 
+  // Clear the selection if the selection manager is available and has an active selection
+  if (this.selectionManager && this.selectionManager.hasSelection) {
+    this.selectionManager.clearSelection();
+  }
+
   // Input is being sent to the terminal, the terminal should focus the prompt.
-  if (this.ybase !== this.ydisp) {
+  if (this.buffer.ybase !== this.buffer.ydisp) {
     this.scrollToBottom();
   }
   this.emit('data', data);
@@ -2209,14 +2182,14 @@ Terminal.prototype.handleTitle = function(title) {
  * ESC D Index (IND is 0x84).
  */
 Terminal.prototype.index = function() {
-  this.y++;
-  if (this.y > this.scrollBottom) {
-    this.y--;
+  this.buffer.y++;
+  if (this.buffer.y > this.buffer.scrollBottom) {
+    this.buffer.y--;
     this.scroll();
   }
   // If the end of the line is hit, prevent this action from wrapping around to the next line.
-  if (this.x >= this.cols) {
-    this.x--;
+  if (this.buffer.x >= this.cols) {
+    this.buffer.x--;
   }
 };
 
@@ -2228,16 +2201,16 @@ Terminal.prototype.index = function() {
  */
 Terminal.prototype.reverseIndex = function() {
   var j;
-  if (this.y === this.scrollTop) {
+  if (this.buffer.y === this.buffer.scrollTop) {
     // possibly move the code below to term.reverseScroll();
     // test: echo -ne '\e[1;1H\e[44m\eM\e[0m'
     // blankLine(true) is xterm/linux behavior
-    this.lines.shiftElements(this.y + this.ybase, this.rows - 1, 1);
-    this.lines.set(this.y + this.ybase, this.blankLine(true));
-    this.updateRange(this.scrollTop);
-    this.updateRange(this.scrollBottom);
+    this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, this.rows - 1, 1);
+    this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true));
+    this.updateRange(this.buffer.scrollTop);
+    this.updateRange(this.buffer.scrollBottom);
   } else {
-    this.y--;
+    this.buffer.y--;
   }
 };
 
@@ -2248,11 +2221,13 @@ 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;
+  var inputHandler = this.inputHandler;
   Terminal.call(this, this.options);
-  this.customKeydownHandler = customKeydownHandler;
+  this.customKeyEventHandler = customKeyEventHandler;
   this.cursorBlinkInterval = cursorBlinkInterval;
+  this.inputHandler = inputHandler;
   this.refresh(0, this.rows - 1);
   this.viewport.syncScrollArea();
 };
@@ -2262,7 +2237,7 @@ Terminal.prototype.reset = function() {
  * ESC H Tab Set (HTS is 0x88).
  */
 Terminal.prototype.tabSet = function() {
-  this.tabs[this.x] = true;
+  this.buffer.tabs[this.buffer.x] = true;
 };
 
 /**
@@ -2398,6 +2373,7 @@ function keys(obj) {
  * Expose
  */
 
+Terminal.translateBufferLineToString = translateBufferLineToString;
 Terminal.EventEmitter = EventEmitter;
 Terminal.inherits = inherits;