]> git.proxmox.com Git - mirror_xterm.js.git/commitdiff
Merge remote-tracking branch 'upstream/master' into 119_fix_cursor_blink
authorDaniel Imms <tyriar@tyriar.com>
Tue, 14 Jun 2016 17:36:56 +0000 (10:36 -0700)
committerDaniel Imms <tyriar@tyriar.com>
Tue, 14 Jun 2016 17:36:56 +0000 (10:36 -0700)
.gitignore
addons/linkify/linkify.js
conf.json [deleted file]
demo/index.html
demo/main.js
jsdoc.json [new file with mode: 0644]
package.json
src/xterm.js
test/addons/linkify-test.js [new file with mode: 0644]
test/addons/test.js [new file with mode: 0644]
test/test.js

index af5c7e2e9acbcb1343aee52c879ac3c84fb9ddda..029efd56b8536c4438a5a46baed01672599cc05a 100644 (file)
@@ -8,5 +8,5 @@ Makefile.gyp
 *.target.gyp.mk
 *.node
 example/*.log
-docs/_build
+docs/
 npm-debug.log
index 152404d447e211793cca1c60c362930960c5f15b..f02c2023c2aaed2a757f7bf3d5333aeb3810c2a9 100644 (file)
       negatedDomainCharacterSet = '[^\\da-z\\.-]+',
       domainBodyClause = '(' + domainCharacterSet + ')',
       tldClause = '([a-z\\.]{2,6})',
-      hostClause = domainBodyClause + '\\.' + tldClause,
-      pathClause = '([\\/\\w\\.-]*)*\\/?',
+      ipClause = '((\\d{1,3}\\.){3}\\d{1,3})',
+      portClause = '(:\\d{1,5})',
+      hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + ')' + portClause + '?',
+      pathClause = '(\\/[\\/\\w\\.-]*)*',
       negatedPathCharacterSet = '[^\\/\\w\\.-]+',
       bodyClause = hostClause + pathClause,
       start = '(?:^|' + negatedDomainCharacterSet + ')(',
@@ -41,7 +43,6 @@
       lenientUrlRegex = new RegExp(lenientUrlClause),
       strictUrlRegex = new RegExp(strictUrlClause);
 
-
   /**
    * Converts all valid URLs found in the given terminal line into
    * hyperlinks. The terminal line can be either the HTML element itself
         continue;
       }
 
+      var url = exports.findLinkMatch(node.data, lenient);
 
-      if (lenient) {
-        match = node.data.match(lenientUrlRegex);
-      } else {
-        match = node.data.match(strictUrlRegex);
-      }
-
-      /**
-       * If no URL was found in the current text, return.
-       */
-      if (!match) {
+      if (!url) {
         continue;
       }
 
-      var url = match[1],
-          startsWithProtocol = new RegExp('^' + protocolClause),
+      var startsWithProtocol = new RegExp('^' + protocolClause),
           urlHasProtocol = url.match(startsWithProtocol),
           href = (urlHasProtocol) ? url : 'http://' + url,
           link = '<a href="' +  href + '" >' + url + '</a>',
     terminal.emit('linkify:line', line);
   };
 
+  /**
+   * Finds a link within a block of text.
+   *
+   * @param {string} text - The text to search .
+   * @param {boolean} lenient - Whether to use the lenient search.
+   * @return {string} A URL.
+   */
+  exports.findLinkMatch = function (text, lenient) {
+    var match = text.match(lenient ? lenientUrlRegex : strictUrlRegex);
+    if (!match || match.length === 0) {
+      return null;
+    }
+    return match[1];
+  }
 
   /**
    * Converts all valid URLs found in the terminal view into hyperlinks.
diff --git a/conf.json b/conf.json
deleted file mode 100644 (file)
index b5c8e52..0000000
--- a/conf.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-    "source": {
-        "include": [
-            "src/xterm.js",
-            "addons/attach/attach.js",
-            "addons/fit/fit.js",
-            "addons/fullscreen/fullscreen.js",
-            "addons/linkify/linkify.js"
-        ]
-    },
-    "opts": {
-        "readme": "README.md"
-    }
-}
\ No newline at end of file
index 92ccc94ca3636cabf75cd2cfcd9929ea9dff55b7..ff10a5cbd7ba6294c7b4afec8b5ddf7f2266e1a7 100644 (file)
@@ -9,12 +9,16 @@
         <script src="../addons/attach/attach.js" ></script>
         <script src="../addons/fit/fit.js" ></script>
         <script src="../addons/fullscreen/fullscreen.js" ></script>
-        <script src="main.js" defer ></script>
     </head>
     <body>
         <h1>
             xterm.js: xterm, in the browser
         </h1>
         <div id="terminal-container"></div>
+        <div>
+          <h2>Options</h2>
+          <label><input type="checkbox" id="option-cursor-blink"> cursorBlink</label>
+        </div>
+        <script src="main.js" defer ></script>
     </body>
 </html>
index 70d64c5900c83767a493530c74a1f2e12a892025..59bdacdd226a1e257ddf97e30628f8823f1727e8 100644 (file)
@@ -1,11 +1,36 @@
-var terminalContainer = document.getElementById('terminal-container'),
-    term = new Terminal(),
-    protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://',
-    socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/bash',
-    socket = new WebSocket(socketURL);
+var term,
+    protocol,
+    socketURL,
+    socket;
+
+var terminalContainer = document.getElementById('terminal-container');
+var optionElements = {
+  cursorBlink: document.querySelector('#option-cursor-blink')
+};
+
+optionElements.cursorBlink.addEventListener('change', createTerminal);
+
+createTerminal();
+
+function createTerminal() {
+  while (terminalContainer.children.length) {
+    terminalContainer.removeChild(terminalContainer.children[0]);
+  }
+  term = new Terminal({
+    cursorBlink: optionElements.cursorBlink.checked
+  });
+  protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
+  socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/bash';
+  socket = new WebSocket(socketURL);
+
+  term.open(terminalContainer);
+  term.fit();
+
+  socket.onopen = runRealTerminal;
+  socket.onclose = runFakeTerminal;
+  socket.onerror = runFakeTerminal;
+}
 
-term.open(terminalContainer);
-term.fit();
 
 function runRealTerminal() {
   term.attach(socket);
@@ -54,7 +79,3 @@ function runFakeTerminal() {
     term.write(data);
   });
 }
-
-socket.onopen = runRealTerminal;
-socket.onclose = runFakeTerminal;
-socket.onerror = runFakeTerminal;
diff --git a/jsdoc.json b/jsdoc.json
new file mode 100644 (file)
index 0000000..28ca16c
--- /dev/null
@@ -0,0 +1,23 @@
+{
+  "source": {
+    "include": [
+      "src/xterm.js",
+      "addons/attach/attach.js",
+      "addons/fit/fit.js",
+      "addons/fullscreen/fullscreen.js",
+      "addons/linkify/linkify.js"
+    ]
+  },
+  "opts": {
+    "readme": "README.md",
+    "template": "node_modules/docdash",
+    "encoding": "utf8",
+    "destination": "docs/",
+    "recurse": true,
+    "verbose": true
+  },
+  "templates": {
+    "cleverLinks": false,
+    "monospaceLinks": false
+  }
+}
index 73fde3c7c3dd96f908e8abb97f83c1d15989252a..1b98517c286ff8f23fa65dba84505183c0ad2a5a 100644 (file)
     "express-ws": "2.0.0-rc.1",
     "pty.js": "0.3.0",
     "mocha": "2.5.3",
-    "chai": "3.5.0"
+    "chai": "3.5.0",
+    "jsdoc": "3.4.0",
+    "docdash": "0.4.0"
   },
   "scripts": {
     "start": "bash bin/server",
-    "test": "bash bin/test"
+    "test": "bash bin/test --recursive",
+    "build:docs": "node_modules/.bin/jsdoc -c jsdoc.json"
   }
 }
index 22106e5e3e216ea2e339fa2b2e4a153bc982ebaf..1ac384c8494d1f10d20d7fd38c21033c98653ebe 100644 (file)
      * Terminal
      */
 
+    /**
+     * Creates a new `Terminal` object.
+     *
+     * @param {object} options An object containing a set of options, the available options are:
+     *   - cursorBlink (boolean): Whether the terminal cursor blinks
+     *
+     * @public
+     */
     function Terminal(options) {
       var self = this;
 
        */
       this.y = 0;
 
+      /**
+       * Used to debounce the refresh function
+       */
+      this.isRefreshing = false;
+
+      /**
+       * Whether there is a full terminal refresh queued
+       */
+      this.queuedRefresh = false;
+
       this.cursorState = 0;
       this.cursorHidden = false;
       this.convertEol;
 
       this.tabs;
       this.setupStops();
+      this.debounceRefresh();
     }
 
     inherits(Terminal, EventEmitter);
       return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff);
     };
 
+    /**
+     * Allow refresh to execute only approximately 30 times a second. For commands that pass a
+     * significant amount of output to the write function, this prevents the terminal from maxing
+     * out the CPU and making the UI unresponsive. While commands can still run beyond what they do
+     * on the terminal, it is far better with a debounce in place as every single terminal
+     * manipulation does not need to be constructed in the DOM.
+     *
+     * A side-effect of this is that it makes ^C to interrupt a process seem more responsive.
+     */
+    Terminal.prototype.debounceRefresh = function () {
+      var self = this;
+      window.setInterval(function () {
+        self.isRefreshing = false;
+        if (self.queuedRefresh) {
+          // Do a full refresh in case multiple refreshes were requested.
+          self.refresh(0, self.rows - 1);
+        }
+      }, 34);
+    };
+
     /**
      * Colors
      */
     });
 
     /**
-     * Focused Terminal
+     * Focus the terminal.
+     *
+     * @public
      */
     Terminal.prototype.focus = function() {
       if (document.activeElement === this.element) {
     };
 
 
-    /*
-     * Open Terminal in the DOM
+    /**
+     * Opens the terminal within an element.
+     *
+     * @param {HTMLElement} parent The element to create the terminal within.
+     *
+     * @public
      */
     Terminal.prototype.open = function(parent) {
       var self=this, i=0, div;
       this.emit('open');
     };
 
+
+    /**
+     * Attempts to load an add-on using CommonJS or RequireJS (whichever is available).
+     * @param {string} addon The name of the addon to load
+     * @static
+     */
+    Terminal.loadAddon = function(addon, callback) {
+      if (typeof exports === 'object' && typeof module === 'object') {
+        // CommonJS
+        return require(__dirname + '/../addons/' + addon);
+      } else if (typeof define == 'function') {
+        // RequireJS
+        return require(['../addons/' + addon + '/' + addon], callback);
+      } else {
+        console.error('Cannot load a module without a CommonJS or RequireJS environment.');
+        return false;
+      }
+    };
+
+
     // XTerm mouse events
     // http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
     // To better understand these
     };
 
     /**
-     * Destroy Terminal
+     * Destroys the terminal.
+     *
+     * @public
      */
-
     Terminal.prototype.destroy = function() {
       this.readable = false;
       this.writable = false;
      * Next 14 bits: a mask for misc. flags:
      *   1=bold, 2=underline, 4=blink, 8=inverse, 16=invisible
     */
+
+    /**
+     * Refreshes terminal content within two rows (inclusive).
+     *
+     * @param {number} start The row to start from (between 0 and terminal's height terminal - 1)
+     * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1)
+     *
+     * @public
+     */
     Terminal.prototype.refresh = function(start, end) {
       var x, y, i, line, out, ch, width, data, attr, bg, fg, flags, row, parent, focused = document.activeElement;
 
+      if (this.isRefreshing) {
+        this.queuedRefresh = true;
+        return;
+      }
+      this.isRefreshing = true;
+
       if (end - start >= this.rows / 2) {
         parent = this.element.parentNode;
         if (parent) parent.removeChild(this.element);
       width = this.cols;
       y = start;
 
-      if (end >= this.lines.length) {
+      if (end >= this.rows.length) {
         this.log('`end` is too large. Most likely a bad CSR.');
-        end = this.lines.length - 1;
+        end = this.rows.length - 1;
       }
 
       for (; y <= end; y++) {
       this.refresh(0, this.rows - 1);
     };
 
+    /**
+     * Writes text to the terminal.
+     *
+     * @param {string} text The text to write to the terminal.
+     *
+     * @public
+     */
     Terminal.prototype.write = function(data) {
       var l = data.length, i = 0, j, cs, ch;
 
       this.context.console.error.apply(this.context.console, args);
     };
 
+    /**
+     * Resizes the terminal.
+     *
+     * @param {number} x The number of columns to resize to.
+     * @param {number} y The number of rows to resize to.
+     *
+     * @public
+     */
     Terminal.prototype.resize = function(x, y) {
       var line
         , el
 
     Terminal.EventEmitter = EventEmitter;
     Terminal.inherits = inherits;
+
+    /**
+     * Adds an event listener to the terminal.
+     *
+     * @param {string} event The name of the event. TODO: Document all event types
+     * @param {function} callback The function to call when the event is triggered.
+     *
+     * @public
+     */
     Terminal.on = on;
     Terminal.off = off;
     Terminal.cancel = cancel;
diff --git a/test/addons/linkify-test.js b/test/addons/linkify-test.js
new file mode 100644 (file)
index 0000000..7cb2318
--- /dev/null
@@ -0,0 +1,48 @@
+var assert = require('chai').assert;
+var Terminal = require('../../src/xterm');
+var linkify = require('../../addons/linkify/linkify');
+
+describe('linkify addon', function () {
+  var xterm;
+
+  describe('API', function () {
+    it('should define Terminal.prototype.linkify', function () {
+      assert.isDefined(Terminal.prototype.linkify);
+    });
+    it('should define Terminal.prototype.linkifyTerminalLine', function () {
+      assert.isDefined(Terminal.prototype.linkifyTerminalLine);
+    });
+  });
+
+  describe('findUrlMatchOnLine', function () {
+    describe('strict regex', function () {
+      it('should match when the entire text is a match', function () {
+        assert.equal(linkify.findLinkMatch('http://github.com', false), 'http://github.com');
+        assert.equal(linkify.findLinkMatch('http://127.0.0.1', false), 'http://127.0.0.1');
+      });
+      it('should match simple domains', function () {
+        assert.equal(linkify.findLinkMatch('foo http://github.com bar', false), 'http://github.com');
+        assert.equal(linkify.findLinkMatch('foo http://www.github.com bar', false), 'http://www.github.com');
+        assert.equal(linkify.findLinkMatch('foo https://github.com bar', false), 'https://github.com');
+        assert.equal(linkify.findLinkMatch('foo https://www.github.com bar', false), 'https://www.github.com');
+      });
+      it('should match web addresses with alpha paths', function () {
+        assert.equal(linkify.findLinkMatch('foo http://github.com/a/b/c bar', false), 'http://github.com/a/b/c');
+        assert.equal(linkify.findLinkMatch('foo http://www.github.com/a/b/c bar', false), 'http://www.github.com/a/b/c');
+      });
+      it('should not include whitespace surrounding a match', function () {
+        assert.equal(linkify.findLinkMatch(' http://github.com', false), 'http://github.com');
+        assert.equal(linkify.findLinkMatch('http://github.com ', false), 'http://github.com');
+        assert.equal(linkify.findLinkMatch('  http://github.com  ', false), 'http://github.com');
+      });
+      it('should match IP addresses', function () {
+        assert.equal(linkify.findLinkMatch('foo http://127.0.0.1 bar', false), 'http://127.0.0.1');
+        assert.equal(linkify.findLinkMatch('foo https://127.0.0.1 bar', false), 'https://127.0.0.1');
+      });
+      it('should match ports on both domains and IP addresses', function () {
+        assert.equal(linkify.findLinkMatch('foo http://127.0.0.1:8080 bar', false), 'http://127.0.0.1:8080');
+        assert.equal(linkify.findLinkMatch('foo http://www.github.com:8080 bar', false), 'http://www.github.com:8080');
+      });
+    });
+  });
+});
diff --git a/test/addons/test.js b/test/addons/test.js
new file mode 100644 (file)
index 0000000..ba689e6
--- /dev/null
@@ -0,0 +1,10 @@
+var assert = require('chai').assert;
+var Terminal = require('../../src/xterm');
+
+describe('xterm.js addons', function() {
+  it('should load addons with Terminal.loadAddon', function () {
+    Terminal.loadAddon('attach');
+    // Test that function was loaded successfully
+    assert.equal(typeof Terminal.prototype.attach, 'function');
+  });
+});
index 6f7a07914d8bc6b85899cdbf2cfc496ac48d8d95..fcfd8832408a11da5abb73e6233ef1a2cd18609a 100644 (file)
@@ -79,7 +79,7 @@ describe('xterm.js', function() {
       xterm.handler = function() {};
       xterm.showCursor = function() {};
       xterm.clearSelection = function() {};
-    })
+    });
 
     describe('On Mac OS', function() {
       beforeEach(function() {