]> git.proxmox.com Git - mirror_xterm.js.git/blobdiff - src/xterm.js
Add a comment
[mirror_xterm.js.git] / src / xterm.js
index 8aa51b73665ab8304543d17a97e6f2d090e9e612..4236d4f8f1b98b934ab0e7363ea65719ce3b5aac 100644 (file)
@@ -13,7 +13,7 @@
 import { CompositionHelper } from './CompositionHelper';
 import { EventEmitter } from './EventEmitter';
 import { Viewport } from './Viewport';
-import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard';
+import { moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard';
 import { CircularList } from './utils/CircularList';
 import { C0 } from './EscapeSequences';
 import { InputHandler } from './InputHandler';
@@ -25,6 +25,7 @@ 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';
 
 /**
  * Terminal Emulation References:
@@ -250,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();
@@ -520,28 +525,42 @@ 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) {
+    on(this.element, 'mousedown', event => {
       if (ev.button == 2) {
-        rightClickHandlerWrapper(ev);
+        moveTextAreaUnderMouseCursor(event, this.textarea, this.selectionManager);
       }
     });
   } else {
-    on(this.element, 'contextmenu', rightClickHandlerWrapper);
+    on(this.element, 'contextmenu', event => {
+      moveTextAreaUnderMouseCursor(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, 'click', event => {
+      if (event.button === 1) {
+        moveTextAreaUnderMouseCursor(event, this.textarea, this.selectionManager);
+      }
+    });
   }
 };
 
@@ -691,13 +710,26 @@ Terminal.prototype.open = function(parent, focus) {
 
   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.lines, this.rowContainer, this.selectionContainer, this.charMeasure);
+  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);
@@ -768,10 +800,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() {
+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;}`;
 }
 
 /**
@@ -798,7 +831,7 @@ Terminal.prototype.bindMouse = function() {
     button = getButton(ev);
 
     // get mouse coordinates
-    pos = Mouse.getCoords(ev, this.rowContainer, this.charMeasure);
+    pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);
     if (!pos) return;
 
     sendEvent(button, pos);
@@ -826,7 +859,7 @@ Terminal.prototype.bindMouse = function() {
     var button = pressed
     , pos;
 
-    pos = Mouse.getCoords(ev, this.rowContainer, this.charMeasure);
+    pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);
     if (!pos) return;
 
     // buttons marked as motions
@@ -1001,50 +1034,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.rowContainer;
-
-  //   // 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;
 
@@ -1152,8 +1141,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
@@ -1180,10 +1171,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) {
@@ -1218,6 +1209,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;
@@ -1304,7 +1298,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);
@@ -1400,6 +1400,35 @@ Terminal.prototype.deregisterLinkMatcher = function(matcherId) {
   }
 }
 
+/**
+ * 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
  * Key Resources:
@@ -1731,6 +1760,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;
   }
@@ -2100,8 +2133,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;
@@ -2110,6 +2144,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;
   }