]> git.proxmox.com Git - mirror_xterm.js.git/commitdiff
Pull CompositionHelper into a module
authorDaniel Imms <tyriar@tyriar.com>
Mon, 29 Aug 2016 23:15:28 +0000 (16:15 -0700)
committerDaniel Imms <tyriar@tyriar.com>
Mon, 29 Aug 2016 23:15:28 +0000 (16:15 -0700)
Fixes #254

src/CompositionHelper.js [new file with mode: 0644]
src/xterm.js

diff --git a/src/CompositionHelper.js b/src/CompositionHelper.js
new file mode 100644 (file)
index 0000000..46bdab9
--- /dev/null
@@ -0,0 +1,196 @@
+/**
+ * Encapsulates the logic for handling compositionstart, compositionupdate and compositionend
+ * events, displaying the in-progress composition to the UI and forwarding the final composition
+ * to the handler.
+ * @param {HTMLTextAreaElement} textarea The textarea that xterm uses for input.
+ * @param {HTMLElement} compositionView The element to display the in-progress composition in.
+ * @param {Terminal} terminal The Terminal to forward the finished composition to.
+ */
+function CompositionHelper(textarea, compositionView, terminal) {
+  this.textarea = textarea;
+  this.compositionView = compositionView;
+  this.terminal = terminal;
+
+  // Whether input composition is currently happening, eg. via a mobile keyboard, speech input
+  // or IME. This variable determines whether the compositionText should be displayed on the UI.
+  this.isComposing = false;
+
+  // The input currently being composed, eg. via a mobile keyboard, speech input or IME.
+  this.compositionText = null;
+
+  // The position within the input textarea's value of the current composition.
+  this.compositionPosition = { start: null, end: null };
+
+  // Whether a composition is in the process of being sent, setting this to false will cancel
+  // any in-progress composition.
+  this.isSendingComposition = false;
+}
+
+/**
+ * Handles the compositionstart event, activating the composition view.
+ */
+CompositionHelper.prototype.compositionstart = function() {
+  this.isComposing = true;
+  this.compositionPosition.start = this.textarea.value.length;
+  this.compositionView.textContent = '';
+  this.compositionView.classList.add('active');
+};
+
+/**
+ * Handles the compositionupdate event, updating the composition view.
+ * @param {CompositionEvent} ev The event.
+ */
+CompositionHelper.prototype.compositionupdate = function(ev) {
+  this.compositionView.textContent = ev.data;
+  this.updateCompositionElements();
+  var self = this;
+  setTimeout(function() {
+    self.compositionPosition.end = self.textarea.value.length;
+  }, 0);
+};
+
+/**
+ * Handles the compositionend event, hiding the composition view and sending the composition to
+ * the handler.
+ */
+CompositionHelper.prototype.compositionend = function() {
+  this.finalizeComposition(true);
+};
+
+/**
+ * Handles the keydown event, routing any necessary events to the CompositionHelper functions.
+ * @return Whether the Terminal should continue processing the keydown event.
+ */
+CompositionHelper.prototype.keydown = function(ev) {
+  if (this.isComposing || this.isSendingComposition) {
+    if (ev.keyCode === 229) {
+      // Continue composing if the keyCode is the "composition character"
+      return false;
+    } else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
+      // Continue composing if the keyCode is a modifier key
+      return false;
+    } else {
+      // Finish composition immediately. This is mainly here for the case where enter is
+      // pressed and the handler needs to be triggered before the command is executed.
+      this.finalizeComposition(false);
+    }
+  }
+
+  if (ev.keyCode === 229) {
+    // If the "composition character" is used but gets to this point it means a non-composition
+    // character (eg. numbers and punctuation) was pressed when the IME was active.
+    this.handleAnyTextareaChanges();
+    return false;
+  }
+
+  return true;
+};
+
+/**
+ * Finalizes the composition, resuming regular input actions. This is called when a composition
+ * is ending.
+ * @param {boolean} waitForPropogation Whether to wait for events to propogate before sending
+ *   the input. This should be false if a non-composition keystroke is entered before the
+ *   compositionend event is triggered, such as enter, so that the composition is send before
+ *   the command is executed.
+ */
+CompositionHelper.prototype.finalizeComposition = function(waitForPropogation) {
+  this.compositionView.classList.remove('active');
+  this.isComposing = false;
+  this.clearTextareaPosition();
+
+  if (!waitForPropogation) {
+    // Cancel any delayed composition send requests and send the input immediately.
+    this.isSendingComposition = false;
+    var input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);
+    this.terminal.handler(input);
+  } else {
+    // Make a deep copy of the composition position here as a new compositionstart event may
+    // fire before the setTimeout executes.
+    var currentCompositionPosition = {
+      start: this.compositionPosition.start,
+      end: this.compositionPosition.end,
+    }
+
+    // Since composition* events happen before the changes take place in the textarea on most
+    // browsers, use a setTimeout with 0ms time to allow the native compositionend event to
+    // complete. This ensures the correct character is retrieved, this solution was used
+    // because:
+    // - The compositionend event's data property is unreliable, at least on Chromium
+    // - The last compositionupdate event's data property does not always accurately describe
+    //   the character, a counter example being Korean where an ending consonsant can move to
+    //   the following character if the following input is a vowel.
+    var self = this;
+    this.isSendingComposition = true;
+    setTimeout(function () {
+      // Ensure that the input has not already been sent
+      if (self.isSendingComposition) {
+        self.isSendingComposition = false;
+        var input;
+        if (self.isComposing) {
+          // Use the end position to get the string if a new composition has started.
+          input = self.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);
+        } else {
+          // Don't use the end position here in order to pick up any characters after the
+          // composition has finished, for example when typing a non-composition character
+          // (eg. 2) after a composition character.
+          input = self.textarea.value.substring(currentCompositionPosition.start);
+        }
+        self.terminal.handler(input);
+      }
+    }, 0);
+  }
+};
+
+/**
+ * Apply any changes made to the textarea after the current event chain is allowed to complete.
+ * This should be called when not currently composing but a keydown event with the "composition
+ * character" (229) is triggered, in order to allow non-composition text to be entered when an
+ * IME is active.
+ */
+CompositionHelper.prototype.handleAnyTextareaChanges = function() {
+  var oldValue = this.textarea.value;
+  var self = this;
+  setTimeout(function() {
+    // Ignore if a composition has started since the timeout
+    if (!self.isComposing) {
+      var newValue = self.textarea.value;
+      var diff = newValue.replace(oldValue, '');
+      if (diff.length > 0) {
+        self.terminal.handler(diff);
+      }
+    }
+  }, 0);
+};
+
+/**
+ * Positions the composition view on top of the cursor and the textarea just below it (so the
+ * IME helper dialog is positioned correctly).
+ */
+CompositionHelper.prototype.updateCompositionElements = function(dontRecurse) {
+  if (!this.isComposing) {
+    return;
+  }
+  var cursor = this.terminal.element.querySelector('.terminal-cursor');
+  if (cursor) {
+    this.compositionView.style.left = cursor.offsetLeft + 'px';
+    this.compositionView.style.top = cursor.offsetTop + 'px';
+    var compositionViewBounds = this.compositionView.getBoundingClientRect();
+    this.textarea.style.left = cursor.offsetLeft + compositionViewBounds.width + 'px';
+    this.textarea.style.top = (cursor.offsetTop + cursor.offsetHeight) + 'px';
+  }
+  if (!dontRecurse) {
+    setTimeout(this.updateCompositionElements.bind(this, true), 0);
+  }
+};
+
+/**
+ * Clears the textarea's position so that the cursor does not blink on IE.
+ * @private
+ */
+CompositionHelper.prototype.clearTextareaPosition = function() {
+  this.textarea.style.left = '';
+  this.textarea.style.top = '';
+};
+
+export { CompositionHelper };
index f4d26465868b74532a175efbbe5d19dd27067c8d..896fc6a036d8c1b45c182f411c6dcadf344dede1 100644 (file)
@@ -31,6 +31,7 @@
  *   other features.
  */
 
+import { CompositionHelper } from './CompositionHelper.js';
 import { EventEmitter } from './EventEmitter.js';
 
     /**
@@ -47,201 +48,6 @@ import { EventEmitter } from './EventEmitter.js';
     // Let it work inside Node.js for automated testing purposes.
     var document = (typeof window != 'undefined') ? window.document : null;
 
-    /**
-     * Encapsulates the logic for handling compositionstart, compositionupdate and compositionend
-     * events, displaying the in-progress composition to the UI and forwarding the final composition
-     * to the handler.
-     * @param {HTMLTextAreaElement} textarea The textarea that xterm uses for input.
-     * @param {HTMLElement} compositionView The element to display the in-progress composition in.
-     * @param {Terminal} terminal The Terminal to forward the finished composition to.
-     */
-    function CompositionHelper(textarea, compositionView, terminal) {
-      this.textarea = textarea;
-      this.compositionView = compositionView;
-      this.terminal = terminal;
-
-      // Whether input composition is currently happening, eg. via a mobile keyboard, speech input
-      // or IME. This variable determines whether the compositionText should be displayed on the UI.
-      this.isComposing = false;
-
-      // The input currently being composed, eg. via a mobile keyboard, speech input or IME.
-      this.compositionText = null;
-
-      // The position within the input textarea's value of the current composition.
-      this.compositionPosition = { start: null, end: null };
-
-      // Whether a composition is in the process of being sent, setting this to false will cancel
-      // any in-progress composition.
-      this.isSendingComposition = false;
-    }
-
-    /**
-     * Handles the compositionstart event, activating the composition view.
-     */
-    CompositionHelper.prototype.compositionstart = function() {
-      this.isComposing = true;
-      this.compositionPosition.start = this.textarea.value.length;
-      this.compositionView.textContent = '';
-      this.compositionView.classList.add('active');
-    };
-
-    /**
-     * Handles the compositionupdate event, updating the composition view.
-     * @param {CompositionEvent} ev The event.
-     */
-    CompositionHelper.prototype.compositionupdate = function(ev) {
-      this.compositionView.textContent = ev.data;
-      this.updateCompositionElements();
-      var self = this;
-      setTimeout(function() {
-        self.compositionPosition.end = self.textarea.value.length;
-      }, 0);
-    };
-
-    /**
-     * Handles the compositionend event, hiding the composition view and sending the composition to
-     * the handler.
-     */
-    CompositionHelper.prototype.compositionend = function() {
-      this.finalizeComposition(true);
-    };
-
-    /**
-     * Handles the keydown event, routing any necessary events to the CompositionHelper functions.
-     * @return Whether the Terminal should continue processing the keydown event.
-     */
-    CompositionHelper.prototype.keydown = function(ev) {
-      if (this.isComposing || this.isSendingComposition) {
-        if (ev.keyCode === 229) {
-          // Continue composing if the keyCode is the "composition character"
-          return false;
-        } else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
-          // Continue composing if the keyCode is a modifier key
-          return false;
-        } else {
-          // Finish composition immediately. This is mainly here for the case where enter is
-          // pressed and the handler needs to be triggered before the command is executed.
-          this.finalizeComposition(false);
-        }
-      }
-
-      if (ev.keyCode === 229) {
-        // If the "composition character" is used but gets to this point it means a non-composition
-        // character (eg. numbers and punctuation) was pressed when the IME was active.
-        this.handleAnyTextareaChanges();
-        return false;
-      }
-
-      return true;
-    };
-
-    /**
-     * Finalizes the composition, resuming regular input actions. This is called when a composition
-     * is ending.
-     * @param {boolean} waitForPropogation Whether to wait for events to propogate before sending
-     *   the input. This should be false if a non-composition keystroke is entered before the
-     *   compositionend event is triggered, such as enter, so that the composition is send before
-     *   the command is executed.
-     */
-    CompositionHelper.prototype.finalizeComposition = function(waitForPropogation) {
-      this.compositionView.classList.remove('active');
-      this.isComposing = false;
-      this.clearTextareaPosition();
-
-      if (!waitForPropogation) {
-        // Cancel any delayed composition send requests and send the input immediately.
-        this.isSendingComposition = false;
-        var input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);
-        this.terminal.handler(input);
-      } else {
-        // Make a deep copy of the composition position here as a new compositionstart event may
-        // fire before the setTimeout executes.
-        var currentCompositionPosition = {
-          start: this.compositionPosition.start,
-          end: this.compositionPosition.end,
-        }
-
-        // Since composition* events happen before the changes take place in the textarea on most
-        // browsers, use a setTimeout with 0ms time to allow the native compositionend event to
-        // complete. This ensures the correct character is retrieved, this solution was used
-        // because:
-        // - The compositionend event's data property is unreliable, at least on Chromium
-        // - The last compositionupdate event's data property does not always accurately describe
-        //   the character, a counter example being Korean where an ending consonsant can move to
-        //   the following character if the following input is a vowel.
-        var self = this;
-        this.isSendingComposition = true;
-        setTimeout(function () {
-          // Ensure that the input has not already been sent
-          if (self.isSendingComposition) {
-            self.isSendingComposition = false;
-            var input;
-            if (self.isComposing) {
-              // Use the end position to get the string if a new composition has started.
-              input = self.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);
-            } else {
-              // Don't use the end position here in order to pick up any characters after the
-              // composition has finished, for example when typing a non-composition character
-              // (eg. 2) after a composition character.
-              input = self.textarea.value.substring(currentCompositionPosition.start);
-            }
-            self.terminal.handler(input);
-          }
-        }, 0);
-      }
-    };
-
-    /**
-     * Apply any changes made to the textarea after the current event chain is allowed to complete.
-     * This should be called when not currently composing but a keydown event with the "composition
-     * character" (229) is triggered, in order to allow non-composition text to be entered when an
-     * IME is active.
-     */
-    CompositionHelper.prototype.handleAnyTextareaChanges = function() {
-      var oldValue = this.textarea.value;
-      var self = this;
-      setTimeout(function() {
-        // Ignore if a composition has started since the timeout
-        if (!self.isComposing) {
-          var newValue = self.textarea.value;
-          var diff = newValue.replace(oldValue, '');
-          if (diff.length > 0) {
-            self.terminal.handler(diff);
-          }
-        }
-      }, 0);
-    };
-
-    /**
-     * Positions the composition view on top of the cursor and the textarea just below it (so the
-     * IME helper dialog is positioned correctly).
-     */
-    CompositionHelper.prototype.updateCompositionElements = function(dontRecurse) {
-      if (!this.isComposing) {
-        return;
-      }
-      var cursor = this.terminal.element.querySelector('.terminal-cursor');
-      if (cursor) {
-        this.compositionView.style.left = cursor.offsetLeft + 'px';
-        this.compositionView.style.top = cursor.offsetTop + 'px';
-        var compositionViewBounds = this.compositionView.getBoundingClientRect();
-        this.textarea.style.left = cursor.offsetLeft + compositionViewBounds.width + 'px';
-        this.textarea.style.top = (cursor.offsetTop + cursor.offsetHeight) + 'px';
-      }
-      if (!dontRecurse) {
-        setTimeout(this.updateCompositionElements.bind(this, true), 0);
-      }
-    };
-
-    /**
-     * Clears the textarea's position so that the cursor does not blink on IE.
-     * @private
-     */
-    CompositionHelper.prototype.clearTextareaPosition = function() {
-      this.textarea.style.left = '';
-      this.textarea.style.top = '';
-    };
-
     /**
      * Represents the viewport of a terminal, the visible area within the larger buffer of output.
      * Logic for the virtual scroll bar is included in this object.