diff --git a/.vscode/launch.json b/.vscode/launch.json
index c7bf73813..e5bad7b32 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -23,10 +23,7 @@
"type": "chrome",
"request": "launch",
"name": "Demo Client",
- "url": "http://0.0.0.0:3000",
- "windows": {
- "url": "http://127.0.0.1:3000"
- },
+ "url": "http://127.0.0.1:3000",
"webRoot": "${workspaceFolder}/"
},
{
@@ -38,7 +35,11 @@
"run",
"start-debug"
],
- "port": 9229
+ "port": 9229,
+ "serverReadyAction": {
+ "action": "openExternally",
+ "pattern": "App listening to (http://.*?:[0-9]+)"
+ }
}
]
}
diff --git a/README.md b/README.md
index 5378f4430..40de04cf4 100644
--- a/README.md
+++ b/README.md
@@ -136,7 +136,7 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**Kubebox**](https://github.com/astefanutti/kubebox): Terminal console for Kubernetes clusters.
- [**Azure Cloud Shell**](https://shell.azure.com): Azure Cloud Shell is a Microsoft-managed admin machine built on Azure, for Azure.
- [**atom-xterm**](https://atom.io/packages/atom-xterm): Atom plugin for providing terminals inside your Atom workspace.
-- [**rtty**](https://github.com/zhaojh329/rtty): A reverse proxy WebTTY. It is composed of the client and the server.
+- [**rtty**](https://github.com/zhaojh329/rtty): Access your terminals from anywhere via the web.
- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS.
- [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker.
- [**Azure Data Studio**](https://github.com/Microsoft/azuredatastudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux.
@@ -154,6 +154,7 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**webssh**](https://github.com/huashengdun/webssh): Web based ssh client.
- [**info-beamer hosted**](https://info-beamer.com): Uses xterm.js to manage digital signage devices from the web dashboard.
- [**Jumpserver**](https://github.com/jumpserver/luna): Jumpserver Luna project, Jumpserver is a bastion server project, Luna use xterm.js for web terminal emulation.
+- [**LxdMosaic**](https://github.com/turtle0x1/LxdMosaic): Uses xterm.js to give terminal access to containers through LXD
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
diff --git a/demo/client.ts b/demo/client.ts
index a3a912f63..aaaf28294 100644
--- a/demo/client.ts
+++ b/demo/client.ts
@@ -30,7 +30,10 @@ Terminal.applyAddon(fit);
Terminal.applyAddon(fullscreen);
Terminal.applyAddon(search);
Terminal.applyAddon(webLinks);
-Terminal.applyAddon(winptyCompat);
+const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
+if (isWindows) {
+ Terminal.applyAddon(winptyCompat);
+}
let term;
@@ -99,7 +102,9 @@ function createTerminal(): void {
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
term.open(terminalContainer);
- term.winptyCompatInit();
+ if (isWindows) {
+ term.winptyCompatInit();
+ }
term.webLinksInit();
term.fit();
term.focus();
diff --git a/demo/index.html b/demo/index.html
index 12f7cb983..370a51ed5 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -2,8 +2,8 @@
Attention: The demo is a barebones implementation and is designed for xterm.js evaluation purposes only. Exposing the demo to the public as is would introduce security risks for the host.
-
* ZMODEM file transfers are supported via an addon. To try it out, install lrzsz onto the remote peer, then run rz to send from your browser or sz <file> to send from the remote peer.
-
-
-
diff --git a/demo/zmodem/main.js b/demo/zmodem/main.js
deleted file mode 100644
index 619ef2b80..000000000
--- a/demo/zmodem/main.js
+++ /dev/null
@@ -1,388 +0,0 @@
-"use strict";
-
-var term,
- protocol,
- socketURL,
- socket,
- pid;
-
-Terminal.applyAddon(fit);
-Terminal.applyAddon(attach);
-Terminal.applyAddon(zmodem);
-Terminal.applyAddon(search);
-
-var terminalContainer = document.getElementById('terminal-container'),
- actionElements = {
- findNext: document.querySelector('#find-next'),
- findPrevious: document.querySelector('#find-previous')
- },
- optionElements = {
- cursorBlink: document.querySelector('#option-cursor-blink'),
- cursorStyle: document.querySelector('#option-cursor-style'),
- scrollback: document.querySelector('#option-scrollback'),
- tabstopwidth: document.querySelector('#option-tabstopwidth'),
- bellStyle: document.querySelector('#option-bell-style')
- },
- colsElement = document.getElementById('cols'),
- rowsElement = document.getElementById('rows');
-
-function setTerminalSize() {
- var cols = parseInt(colsElement.value, 10);
- var rows = parseInt(rowsElement.value, 10);
- var viewportElement = document.querySelector('.xterm-viewport');
- var scrollBarWidth = viewportElement.offsetWidth - viewportElement.clientWidth;
- var width = (cols * term.charMeasure.width + 20 /*room for scrollbar*/).toString() + 'px';
- var height = (rows * term.charMeasure.height).toString() + 'px';
-
- terminalContainer.style.width = width;
- terminalContainer.style.height = height;
- term.resize(cols, rows);
-}
-
-colsElement.addEventListener('change', setTerminalSize);
-rowsElement.addEventListener('change', setTerminalSize);
-
-actionElements.findNext.addEventListener('keypress', function (e) {
- if (e.key === "Enter") {
- e.preventDefault();
- term.findNext(actionElements.findNext.value);
- }
-});
-actionElements.findPrevious.addEventListener('keypress', function (e) {
- if (e.key === "Enter") {
- e.preventDefault();
- term.findPrevious(actionElements.findPrevious.value);
- }
-});
-
-optionElements.cursorBlink.addEventListener('change', function () {
- term.setOption('cursorBlink', optionElements.cursorBlink.checked);
-});
-optionElements.cursorStyle.addEventListener('change', function () {
- term.setOption('cursorStyle', optionElements.cursorStyle.value);
-});
-optionElements.bellStyle.addEventListener('change', function () {
- term.setOption('bellStyle', optionElements.bellStyle.value);
-});
-optionElements.scrollback.addEventListener('change', function () {
- term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10));
-});
-optionElements.tabstopwidth.addEventListener('change', function () {
- term.setOption('tabStopWidth', parseInt(optionElements.tabstopwidth.value, 10));
-});
-
-createTerminal();
-
-function createTerminal() {
- // Clean terminal
- while (terminalContainer.children.length) {
- terminalContainer.removeChild(terminalContainer.children[0]);
- }
- term = new Terminal({
- cursorBlink: optionElements.cursorBlink.checked,
- scrollback: parseInt(optionElements.scrollback.value, 10),
- tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10)
- });
- term.on('resize', function (size) {
- if (!pid) {
- return;
- }
- var cols = size.cols,
- rows = size.rows,
- url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows;
-
- fetch(url, {method: 'POST'});
- });
- protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
- socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
-
- term.open(terminalContainer);
- term.fit();
-
- // fit is called within a setTimeout, cols and rows need this.
- setTimeout(function () {
- colsElement.value = term.cols;
- rowsElement.value = term.rows;
-
- // Set terminal size again to set the specific dimensions on the demo
- setTerminalSize();
-
- fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then(function (res) {
-
- res.text().then(function (pid) {
- window.pid = pid;
- socketURL += pid;
- socket = new WebSocket(socketURL);
- socket.onopen = runRealTerminal;
- socket.onclose = runFakeTerminal;
- socket.onerror = runFakeTerminal;
-
- term.zmodemAttach(socket, {
- noTerminalWriteOutsideSession: true,
- } );
-
- term.on("zmodemRetract", () => {
- start_form.style.display = "none";
- start_form.onsubmit = null;
- });
-
- term.on("zmodemDetect", (detection) => {
- function do_zmodem() {
- term.detach();
- let zsession = detection.confirm();
-
- var promise;
-
- if (zsession.type === "receive") {
- promise = _handle_receive_session(zsession);
- }
- else {
- promise = _handle_send_session(zsession);
- }
-
- promise.catch( console.error.bind(console) ).then( () => {
- term.attach(socket);
- } );
- }
-
- if (_auto_zmodem()) {
- do_zmodem();
- }
- else {
- start_form.style.display = "";
- start_form.onsubmit = function(e) {
- start_form.style.display = "none";
-
- if (document.getElementById("zmstart_yes").checked) {
- do_zmodem();
- }
- else {
- detection.deny();
- }
- };
- }
- });
- });
- });
- }, 0);
-}
-
-//----------------------------------------------------------------------
-// UI STUFF
-
-function _show_file_info(xfer) {
- var file_info = xfer.get_details();
-
- document.getElementById("name").textContent = file_info.name;
- document.getElementById("size").textContent = file_info.size;
- document.getElementById("mtime").textContent = file_info.mtime;
- document.getElementById("files_remaining").textContent = file_info.files_remaining;
- document.getElementById("bytes_remaining").textContent = file_info.bytes_remaining;
-
- document.getElementById("mode").textContent = "0" + file_info.mode.toString(8);
-
- var xfer_opts = xfer.get_options();
- ["conversion", "management", "transport", "sparse"].forEach( (lbl) => {
- document.getElementById(`zfile_${lbl}`).textContent = xfer_opts[lbl];
- } );
-
- document.getElementById("zm_file").style.display = "";
-}
-function _hide_file_info() {
- document.getElementById("zm_file").style.display = "none";
-}
-
-function _save_to_disk(xfer, buffer) {
- return Zmodem.Browser.save_to_disk(buffer, xfer.get_details().name);
-}
-
-var skipper_button = document.getElementById("zm_progress_skipper");
-var skipper_button_orig_text = skipper_button.textContent;
-
-function _show_progress() {
- skipper_button.disabled = false;
- skipper_button.textContent = skipper_button_orig_text;
-
- document.getElementById("bytes_received").textContent = 0;
- document.getElementById("percent_received").textContent = 0;
-
- document.getElementById("zm_progress").style.display = "";
-}
-
-function _update_progress(xfer) {
- var total_in = xfer.get_offset();
-
- document.getElementById("bytes_received").textContent = total_in;
-
- var percent_received = 100 * total_in / xfer.get_details().size;
- document.getElementById("percent_received").textContent = percent_received.toFixed(2);
-}
-
-function _hide_progress() {
- document.getElementById("zm_progress").style.display = "none";
-}
-
-var start_form = document.getElementById("zm_start");
-
-function _auto_zmodem() {
- return document.getElementById("zmodem-auto").checked;
-}
-
-// END UI STUFF
-//----------------------------------------------------------------------
-
-function _handle_receive_session(zsession) {
- zsession.on("offer", function(xfer) {
- current_receive_xfer = xfer;
-
- _show_file_info(xfer);
-
- var offer_form = document.getElementById("zm_offer");
-
- function on_form_submit() {
- offer_form.style.display = "none";
-
- //START
- //if (offer_form.zmaccept.value) {
- if (_auto_zmodem() || document.getElementById("zmaccept_yes").checked) {
- _show_progress();
-
- var FILE_BUFFER = [];
- xfer.on("input", (payload) => {
- _update_progress(xfer);
- FILE_BUFFER.push( new Uint8Array(payload) );
- });
- xfer.accept().then(
- () => {
- _save_to_disk(xfer, FILE_BUFFER);
- },
- console.error.bind(console)
- );
- }
- else {
- xfer.skip();
- }
- //END
- }
-
- if (_auto_zmodem()) {
- on_form_submit();
- }
- else {
- offer_form.onsubmit = on_form_submit;
- offer_form.style.display = "";
- }
- } );
-
- var promise = new Promise( (res) => {
- zsession.on("session_end", () => {
- _hide_file_info();
- _hide_progress();
- res();
- } );
- } );
-
- zsession.start();
-
- return promise;
-}
-
-function _handle_send_session(zsession) {
- var choose_form = document.getElementById("zm_choose");
- choose_form.style.display = "";
-
- var file_el = document.getElementById("zm_files");
-
- var promise = new Promise( (res) => {
- file_el.onchange = function(e) {
- choose_form.style.display = "none";
-
- var files_obj = file_el.files;
-
- Zmodem.Browser.send_files(
- zsession,
- files_obj,
- {
- on_offer_response(obj, xfer) {
- if (xfer) _show_progress();
- //console.log("offer", xfer ? "accepted" : "skipped");
- },
- on_progress(obj, xfer) {
- _update_progress(xfer);
- },
- on_file_complete(obj) {
- //console.log("COMPLETE", obj);
- _hide_progress();
- },
- }
- ).then(_hide_progress).then(
- zsession.close.bind(zsession),
- console.error.bind(console)
- ).then( () => {
- _hide_file_info();
- _hide_progress();
- res();
- } );
- };
- } );
-
- return promise;
-}
-
-//This is here to allow canceling of an in-progress ZMODEM transfer.
-var current_receive_xfer;
-
-//Called from HTML directly.
-function skip_current_file() {
- current_receive_xfer.skip();
-
- skipper_button.disabled = true;
- skipper_button.textContent = "Waiting for server to acknowledge skip …";
-}
-
-function runRealTerminal() {
- term.attach(socket);
-
- term._initialized = true;
-}
-
-function runFakeTerminal() {
- if (term._initialized) {
- return;
- }
-
- term._initialized = true;
-
- var shellprompt = '$ ';
-
- term.prompt = function () {
- term.write('\r\n' + shellprompt);
- };
-
- term.writeln('Welcome to xterm.js');
- term.writeln('This is a local terminal emulation, without a real terminal in the back-end.');
- term.writeln('Type some keys and commands to play around.');
- term.writeln('');
- term.prompt();
-
- term.on('key', function (key, ev) {
- var printable = (
- !ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey
- );
-
- if (ev.keyCode == 13) {
- term.prompt();
- } else if (ev.keyCode == 8) {
- // Do not delete the prompt
- if (term.x > 2) {
- term.write('\b \b');
- }
- } else if (printable) {
- term.write(key);
- }
- });
-
- term.on('paste', function (data, ev) {
- term.write(data);
- });
-}
diff --git a/docker-compose.yml b/docker-compose.yml
index 8e6a2f46b..6eefed896 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -7,7 +7,7 @@ services:
volumes:
- ./:/usr/src/app
ports:
- - ${XTERMJS_PORT:3000}:3000
+ - ${XTERMJS_PORT:-3000}:3000
command: ["npm", "start"]
watch:
diff --git a/gulpfile.js b/gulpfile.js
index 9af8d6e4a..bbb4d6e16 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -16,9 +16,9 @@ const ts = require('gulp-typescript');
const util = require('gulp-util');
const buildDir = process.env.BUILD_DIR || 'build';
-const tsProject = ts.createProject('tsconfig.json');
-let srcDir = tsProject.config.compilerOptions.rootDir;
-let outDir = tsProject.config.compilerOptions.outDir;
+const tsProject = ts.createProject('src/tsconfig.json');
+let srcDir = './src';
+let outDir = './lib';
const addons = fs.readdirSync(`${__dirname}/src/addons`);
@@ -61,7 +61,7 @@ gulp.task('browserify', function() {
};
let bundleStream = browserify(browserifyOptions)
.bundle()
- .pipe(source('xterm.js'))
+ .pipe(source(`xterm.js`))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true, sourceRoot: '..'}))
.pipe(sourcemaps.write('./'))
@@ -136,6 +136,6 @@ gulp.task('sorcery-addons', ['browserify-addons'], function () {
})
});
-gulp.task('build', ['sorcery', 'sorcery-addons']);
+gulp.task('build', ['css', 'sorcery', 'sorcery-addons']);
gulp.task('test', ['mocha']);
gulp.task('default', ['build']);
diff --git a/package.json b/package.json
index fa9cc0706..539b96daf 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
- "version": "3.11.0",
+ "version": "3.12.0",
"main": "lib/public/Terminal.js",
"types": "typings/xterm.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
@@ -15,7 +15,6 @@
"@types/webpack": "^4.4.11",
"browserify": "^13.3.0",
"chai": "3.5.0",
- "concurrently": "^3.5.1",
"coveralls": "^3.0.1",
"express": "4.13.4",
"express-ws": "2.0.0-rc.1",
@@ -38,7 +37,7 @@
"ts-loader": "^4.5.0",
"tslint": "^5.9.1",
"tslint-consistent-codestyle": "^1.13.0",
- "typescript": "3.1",
+ "typescript": "3.4",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"webpack": "^4.17.1",
@@ -50,20 +49,16 @@
"start-debug": "node --inspect-brk demo/start",
"start-zmodem": "node demo/zmodem/app",
"lint": "tslint 'src/**/*.ts' './demo/**/*.ts'",
- "pretest": "npm run layering",
"test": "npm run mocha",
"posttest": "npm run lint",
"test-debug": "node --inspect-brk node_modules/.bin/gulp test",
"test-suite": "gulp mocha-suite --test",
"test-coverage": "nyc -x gulpfile.js -x '**/*test*' npm run mocha",
"mocha": "gulp test",
- "tsc": "tsc",
- "prebuild": "concurrently --kill-others-on-fail --names \"lib,attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem,css\" \"tsc\" \"tsc -p ./src/addons/attach\" \"tsc -p ./src/addons/fit\" \"tsc -p ./src/addons/fullscreen\" \"tsc -p ./src/addons/search\" \"tsc -p ./src/addons/terminado\" \"tsc -p ./src/addons/webLinks\" \"tsc -p ./src/addons/winptyCompat\" \"tsc -p ./src/addons/zmodem\" \"gulp css\"",
+ "prebuild": "tsc -b ./src/tsconfig.all.json",
"build": "gulp build",
"prepublish": "npm run build",
"coveralls": "nyc report --reporter=text-lcov | coveralls",
- "watch": "concurrently --kill-others-on-fail --names \"lib,css\" \"tsc -w\" \"gulp watch-css\"",
- "watch-addons": "concurrently --kill-others-on-fail --names \"attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem\" \"tsc -w -p ./src/addons/attach\" \"tsc -w -p ./src/addons/fit\" \"tsc -w -p ./src/addons/fullscreen\" \"tsc -w -p ./src/addons/search\" \"tsc -w -p ./src/addons/terminado\" \"tsc -w -p ./src/addons/webLinks\" \"tsc -w -p ./src/addons/winptyCompat\" \"tsc -w -p ./src/addons/zmodem\"",
- "layering": "concurrently --kill-others-on-fail --names \"common,core\" \"tsc -p ./src/common\" \"tsc -p ./src/core\""
+ "watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput"
}
}
diff --git a/src/Buffer.ts b/src/Buffer.ts
index d017aa7e8..5eea5d9a4 100644
--- a/src/Buffer.ts
+++ b/src/Buffer.ts
@@ -211,7 +211,7 @@ export class Buffer implements IBuffer {
this.scrollBottom = newRows - 1;
- if (this._hasScrollback) {
+ if (this._isReflowEnabled) {
this._reflow(newCols, newRows);
// Trim the end of the line off if cols shrunk
@@ -226,6 +226,10 @@ export class Buffer implements IBuffer {
this._rows = newRows;
}
+ private get _isReflowEnabled(): boolean {
+ return this._hasScrollback && !(this._terminal as any).isWinptyCompatEnabled;
+ }
+
private _reflow(newCols: number, newRows: number): void {
if (this._cols === newCols) {
return;
diff --git a/src/Terminal.ts b/src/Terminal.ts
index 5de369feb..c2497df44 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -64,10 +64,12 @@ const document = (typeof window !== 'undefined') ? window.document : null;
const WRITE_BUFFER_PAUSE_THRESHOLD = 5;
/**
- * The number of writes to perform in a single batch before allowing the
- * renderer to catch up with a 0ms setTimeout.
+ * The max number of ms to spend on writes before allowing the renderer to
+ * catch up with a 0ms setTimeout. A value of < 33 to keep us close to
+ * 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS
+ * depends on the time it takes for the renderer to draw the frame.
*/
-const WRITE_BATCH_SIZE = 300;
+const WRITE_TIMEOUT_MS = 12;
const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars
const MINIMUM_ROWS = 1;
@@ -341,7 +343,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
*/
public focus(): void {
if (this.textarea) {
- this.textarea.focus();
+ this.textarea.focus({ preventScroll: true });
}
}
@@ -859,17 +861,11 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
if (ch > 127) ch = 127;
data.push(ch);
} else {
- if (ch === 2047) {
- data.push(0);
+ if (ch > 2047) {
+ data.push(2047);
return;
}
- if (ch < 127) {
- data.push(ch);
- } else {
- if (ch > 2047) ch = 2047;
- data.push(0xC0 | (ch >> 6));
- data.push(0x80 | (ch & 0x3F));
- }
+ data.push(ch);
}
}
@@ -1348,19 +1344,20 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
}
}
- protected _innerWrite(): void {
+ protected _innerWrite(bufferOffset: number = 0): void {
// Ensure the terminal isn't disposed
if (this._isDisposed) {
this.writeBuffer = [];
}
- const writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);
- while (writeBatch.length > 0) {
- const data = writeBatch.shift();
+ const startTime = Date.now();
+ while (this.writeBuffer.length > bufferOffset) {
+ const data = this.writeBuffer[bufferOffset];
+ bufferOffset++;
// If XOFF was sent in order to catch up with the pty process, resume it if
- // the writeBuffer is empty to allow more data to come in.
- if (this._xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {
+ // we reached the end of the writeBuffer to allow more data to come in.
+ if (this._xoffSentToCatchUp && this.writeBuffer.length === bufferOffset) {
this.handler(C0.DC1);
this._xoffSentToCatchUp = false;
}
@@ -1378,12 +1375,17 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
this.updateRange(this.buffer.y);
this.refresh(this._refreshStart, this._refreshEnd);
+
+ if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
+ break;
+ }
}
- if (this.writeBuffer.length > 0) {
+ if (this.writeBuffer.length > bufferOffset) {
// Allow renderer to catch up before processing the next batch
- setTimeout(() => this._innerWrite(), 0);
+ setTimeout(() => this._innerWrite(bufferOffset), 0);
} else {
this._writeInProgress = false;
+ this.writeBuffer = [];
}
}
diff --git a/src/addons/attach/tsconfig.json b/src/addons/attach/tsconfig.json
index 359fbd240..2f39102ca 100644
--- a/src/addons/attach/tsconfig.json
+++ b/src/addons/attach/tsconfig.json
@@ -10,8 +10,7 @@
"outDir": "../../../lib/addons/attach/",
"sourceMap": true,
"removeComments": true,
- "declaration": true,
- "preserveWatchOutput": true
+ "declaration": true
},
"include": [
"**/*.ts",
diff --git a/src/addons/fit/tsconfig.json b/src/addons/fit/tsconfig.json
index 489ccdfea..3458d23a7 100644
--- a/src/addons/fit/tsconfig.json
+++ b/src/addons/fit/tsconfig.json
@@ -11,7 +11,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
- "preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
diff --git a/src/addons/fullscreen/tsconfig.json b/src/addons/fullscreen/tsconfig.json
index 05e6df68d..0c74c25c0 100644
--- a/src/addons/fullscreen/tsconfig.json
+++ b/src/addons/fullscreen/tsconfig.json
@@ -11,7 +11,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
- "preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
diff --git a/src/addons/search/tsconfig.json b/src/addons/search/tsconfig.json
index 87899cda0..6a1611a51 100644
--- a/src/addons/search/tsconfig.json
+++ b/src/addons/search/tsconfig.json
@@ -11,7 +11,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
- "preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
diff --git a/src/addons/terminado/tsconfig.json b/src/addons/terminado/tsconfig.json
index 91c18314e..e2e194457 100644
--- a/src/addons/terminado/tsconfig.json
+++ b/src/addons/terminado/tsconfig.json
@@ -10,7 +10,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
- "preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
diff --git a/src/addons/webLinks/tsconfig.json b/src/addons/webLinks/tsconfig.json
index 18105aa24..9c4f11760 100644
--- a/src/addons/webLinks/tsconfig.json
+++ b/src/addons/webLinks/tsconfig.json
@@ -11,7 +11,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
- "preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
diff --git a/src/addons/winptyCompat/tsconfig.json b/src/addons/winptyCompat/tsconfig.json
index 9fc4d25e3..fa48c963e 100644
--- a/src/addons/winptyCompat/tsconfig.json
+++ b/src/addons/winptyCompat/tsconfig.json
@@ -10,7 +10,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
- "preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts
index aec580edb..58f59fd90 100644
--- a/src/addons/winptyCompat/winptyCompat.ts
+++ b/src/addons/winptyCompat/winptyCompat.ts
@@ -13,11 +13,7 @@ const WHITESPACE_CELL_CODE = 32;
export function winptyCompatInit(terminal: Terminal): void {
const addonTerminal = terminal;
- // Don't do anything when the platform is not Windows
- const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
- if (!isWindows) {
- return;
- }
+ (addonTerminal._core as any).isWinptyCompatEnabled = true;
// Winpty does not support wraparound mode which means that lines will never
// be marked as wrapped. This causes issues for things like copying a line
diff --git a/src/addons/zmodem/tsconfig.json b/src/addons/zmodem/tsconfig.json
index 2b49f537e..7d821b7c5 100644
--- a/src/addons/zmodem/tsconfig.json
+++ b/src/addons/zmodem/tsconfig.json
@@ -10,7 +10,6 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
- "preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha"
]
diff --git a/src/common/tsconfig.json b/src/common/tsconfig.json
index 19dd02731..b40bb2f50 100644
--- a/src/common/tsconfig.json
+++ b/src/common/tsconfig.json
@@ -1,17 +1,7 @@
{
+ "extends": "../tsconfig-library-base",
"compilerOptions": {
- "target": "es5",
- "lib": [
- "es5"
- ],
- "rootDir": ".",
- "noEmit": true,
- "strict": true,
- "pretty": true,
- "types": [
- "../../node_modules/@types/mocha",
- "../../"
- ]
+ "outDir": "../../lib"
},
"include": [
"./**/*"
diff --git a/src/core/tsconfig.json b/src/core/tsconfig.json
index 4f024a28d..41e41f0c1 100644
--- a/src/core/tsconfig.json
+++ b/src/core/tsconfig.json
@@ -1,20 +1,12 @@
{
+ "extends": "../tsconfig-library-base",
"compilerOptions": {
- "target": "es5",
- "lib": [
- "es5"
- ],
- "rootDir": ".",
- "noEmit": true,
- "strict": true,
- "pretty": true,
- "types": [
- "../../node_modules/@types/mocha",
- "../../"
- ]
+ "outDir": "../../lib"
},
"include": [
- "./**/*",
- "../common/**/*"
+ "./**/*"
+ ],
+ "references": [
+ { "path": "../common" }
]
}
diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts
index 3e0b86430..a609d79ce 100644
--- a/src/renderer/BaseRenderLayer.ts
+++ b/src/renderer/BaseRenderLayer.ts
@@ -241,7 +241,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._ctx.fillText(
charData[CHAR_DATA_CHAR_INDEX],
x * this._scaledCellWidth + this._scaledCharLeft,
- (y + 0.5) * this._scaledCellHeight + this._scaledCharTop);
+ y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
}
/**
@@ -316,7 +316,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._ctx.fillText(
chars,
x * this._scaledCellWidth + this._scaledCharLeft,
- (y + 0.5) * this._scaledCellHeight + this._scaledCharTop);
+ y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
this._ctx.restore();
}
diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts
index b8ef87aab..2c1b516ae 100644
--- a/src/renderer/Renderer.ts
+++ b/src/renderer/Renderer.ts
@@ -85,6 +85,7 @@ export class Renderer extends EventEmitter implements IRenderer {
this._isPaused = entry.intersectionRatio === 0;
if (!this._isPaused && this._needsFullRefresh) {
this._terminal.refresh(0, this._terminal.rows - 1);
+ this._needsFullRefresh = false;
}
}
diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts
index c5ef212da..78ccc620e 100644
--- a/src/renderer/dom/DomRenderer.ts
+++ b/src/renderer/dom/DomRenderer.ts
@@ -9,7 +9,7 @@ import { ITheme } from 'xterm';
import { EventEmitter } from '../../common/EventEmitter';
import { ColorManager } from '../ColorManager';
import { RenderDebouncer } from '../../ui/RenderDebouncer';
-import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory';
+import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory';
import { INVERTED_DEFAULT_COLOR } from '../atlas/Types';
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
@@ -75,7 +75,7 @@ export class DomRenderer extends EventEmitter implements IRenderer {
this._updateDimensions();
this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this));
- this._rowFactory = new DomRendererRowFactory(document);
+ this._rowFactory = new DomRendererRowFactory(_terminal.options, document);
this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
this._terminal.screenElement.appendChild(this._rowContainer);
@@ -165,12 +165,22 @@ export class DomRenderer extends EventEmitter implements IRenderer {
`${this._terminalSelector} span.${ITALIC_CLASS} {` +
` font-style: italic;` +
`}`;
+ // Blink animation
+ styles +=
+ `@keyframes blink {` +
+ ` 0 % { opacity: 1.0; }` +
+ ` 50% { opacity: 0.0; }` +
+ ` 100 % { opacity: 1.0; }` +
+ `}`;
// Cursor
styles +=
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS} {` +
` outline: 1px solid ${this.colorManager.colors.cursor.css};` +
` outline-offset: -1px;` +
`}` +
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS} {` +
+ ` animation: blink 1s step-end infinite;` +
+ `}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
` background-color: ${this.colorManager.colors.cursor.css};` +
` color: ${this.colorManager.colors.cursorAccent.css};` +
@@ -328,6 +338,7 @@ export class DomRenderer extends EventEmitter implements IRenderer {
const cursorAbsoluteY = terminal.buffer.ybase + terminal.buffer.y;
const cursorX = this._terminal.buffer.x;
+ const cursorBlink = this._terminal.options.cursorBlink;
for (let y = start; y <= end; y++) {
const rowElement = this._rowElements[y];
@@ -336,7 +347,7 @@ export class DomRenderer extends EventEmitter implements IRenderer {
const row = y + terminal.buffer.ydisp;
const lineData = terminal.buffer.lines.get(row);
const cursorStyle = terminal.options.cursorStyle;
- rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, this.dimensions.actualCellWidth, terminal.cols));
+ rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, terminal.cols));
}
this._terminal.emit('refresh', {start, end});
diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts
index 67342da01..b7c6876d4 100644
--- a/src/renderer/dom/DomRendererRowFactory.test.ts
+++ b/src/renderer/dom/DomRendererRowFactory.test.ts
@@ -9,23 +9,28 @@ import { DomRendererRowFactory } from './DomRendererRowFactory';
import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '../../Buffer';
import { FLAGS } from '../Types';
import { BufferLine } from '../../BufferLine';
-import { IBufferLine } from '../../Types';
+import { IBufferLine, ITerminalOptions } from '../../Types';
import { DEFAULT_COLOR } from '../atlas/Types';
describe('DomRendererRowFactory', () => {
let dom: jsdom.JSDOM;
+ const options: ITerminalOptions = {};
let rowFactory: DomRendererRowFactory;
let lineData: IBufferLine;
beforeEach(() => {
dom = new jsdom.JSDOM('');
- rowFactory = new DomRendererRowFactory(dom.window.document);
+
+ options.enableBold = true;
+ options.drawBoldTextInBrightColors = true;
+
+ rowFactory = new DomRendererRowFactory(options, dom.window.document);
lineData = createEmptyLineData(2);
});
describe('createRow', () => {
it('should not create anything for an empty row', () => {
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
''
);
@@ -35,7 +40,7 @@ describe('DomRendererRowFactory', () => {
lineData.set(0, [DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)]);
// There should be no element for the following "empty" cell
lineData.set(1, [DEFAULT_ATTR, '', 0, undefined]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'語'
);
@@ -43,17 +48,24 @@ describe('DomRendererRowFactory', () => {
it('should add class for cursor and cursor style', () => {
for (const style of ['block', 'bar', 'underline']) {
- const fragment = rowFactory.createRow(lineData, true, style, 0, 5, 20);
+ const fragment = rowFactory.createRow(lineData, true, style, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
``
);
}
});
+ it('should add class for cursor blink', () => {
+ const fragment = rowFactory.createRow(lineData, true, 'block', 0, true, 5, 20);
+ assert.equal(getFragmentHtml(fragment),
+ ``
+ );
+ });
+
it('should not render cells that go beyond the terminal\'s columns', () => {
lineData.set(0, [DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]);
lineData.set(1, [DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 1);
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 1);
assert.equal(getFragmentHtml(fragment),
'a'
);
@@ -62,7 +74,7 @@ describe('DomRendererRowFactory', () => {
describe('attributes', () => {
it('should add class for bold', () => {
lineData.set(0, [DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'a'
);
@@ -70,7 +82,7 @@ describe('DomRendererRowFactory', () => {
it('should add class for italic', () => {
lineData.set(0, [DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'a'
);
@@ -80,7 +92,7 @@ describe('DomRendererRowFactory', () => {
const defaultAttrNoFgColor = (0 << 9) | (DEFAULT_COLOR << 0);
for (let i = 0; i < 256; i++) {
lineData.set(0, [defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`a`
);
@@ -91,7 +103,7 @@ describe('DomRendererRowFactory', () => {
const defaultAttrNoBgColor = (DEFAULT_ATTR << 9) | (0 << 0);
for (let i = 0; i < 256; i++) {
lineData.set(0, [defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`a`
);
@@ -100,7 +112,7 @@ describe('DomRendererRowFactory', () => {
it('should correctly invert colors', () => {
lineData.set(0, [(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'a'
);
@@ -108,7 +120,7 @@ describe('DomRendererRowFactory', () => {
it('should correctly invert default fg color', () => {
lineData.set(0, [(FLAGS.INVERSE << 18) | (DEFAULT_ATTR << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'a'
);
@@ -116,7 +128,7 @@ describe('DomRendererRowFactory', () => {
it('should correctly invert default bg color', () => {
lineData.set(0, [(FLAGS.INVERSE << 18) | (1 << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'a'
);
@@ -125,7 +137,7 @@ describe('DomRendererRowFactory', () => {
it('should turn bold fg text bright', () => {
for (let i = 0; i < 8; i++) {
lineData.set(0, [(FLAGS.BOLD << 18) | (i << 9) | (DEFAULT_COLOR << 0), 'a', 1, 'a'.charCodeAt(0)]);
- const fragment = rowFactory.createRow(lineData, false, undefined, 0, 5, 20);
+ const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`a`
);
diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts
index 8bcde39a0..1a67858c4 100644
--- a/src/renderer/dom/DomRendererRowFactory.ts
+++ b/src/renderer/dom/DomRendererRowFactory.ts
@@ -5,23 +5,25 @@
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../Buffer';
import { FLAGS } from '../Types';
-import { IBufferLine } from '../../Types';
+import { IBufferLine, ITerminalOptions } from '../../Types';
import { DEFAULT_COLOR, INVERTED_DEFAULT_COLOR } from '../atlas/Types';
export const BOLD_CLASS = 'xterm-bold';
export const ITALIC_CLASS = 'xterm-italic';
export const CURSOR_CLASS = 'xterm-cursor';
+export const CURSOR_BLINK_CLASS = 'xterm-cursor-blink';
export const CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block';
export const CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar';
export const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline';
export class DomRendererRowFactory {
constructor(
+ private _terminalOptions: ITerminalOptions,
private _document: Document
) {
}
- public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cellWidth: number, cols: number): DocumentFragment {
+ public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment {
const fragment = this._document.createDocumentFragment();
// Find the line length first, this prevents the need to output a bunch of
@@ -62,6 +64,10 @@ export class DomRendererRowFactory {
if (isCursorRow && x === cursorX) {
charElement.classList.add(CURSOR_CLASS);
+ if (cursorBlink) {
+ charElement.classList.add(CURSOR_BLINK_CLASS);
+ }
+
switch (cursorStyle) {
case 'bar':
charElement.classList.add(CURSOR_STYLE_BAR_CLASS);
@@ -88,10 +94,10 @@ export class DomRendererRowFactory {
}
}
- if (flags & FLAGS.BOLD) {
+ if (flags & FLAGS.BOLD && this._terminalOptions.enableBold) {
// Convert the FG color to the bold variant. This should not happen when
// the fg is the inverse default color as there is no bold variant.
- if (fg < 8) {
+ if (fg < 8 && this._terminalOptions.drawBoldTextInBrightColors) {
fg += 8;
}
charElement.classList.add(BOLD_CLASS);
diff --git a/src/tsconfig-base.json b/src/tsconfig-base.json
new file mode 100644
index 000000000..5c6afcc5b
--- /dev/null
+++ b/src/tsconfig-base.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [ "es5" ],
+ "rootDir": ".",
+
+ "sourceMap": true,
+ "removeComments": true,
+ "pretty": true,
+
+ "incremental": true,
+
+ "skipLibCheck": true
+ }
+}
diff --git a/src/tsconfig-library-base.json b/src/tsconfig-library-base.json
new file mode 100644
index 000000000..c82e08735
--- /dev/null
+++ b/src/tsconfig-library-base.json
@@ -0,0 +1,11 @@
+{
+ "extends": "./tsconfig-base.json",
+ "compilerOptions": {
+ "types": [
+ "../../node_modules/@types/mocha",
+ "../../"
+ ],
+ "composite": true,
+ "strict": true
+ }
+}
diff --git a/src/tsconfig.all.json b/src/tsconfig.all.json
new file mode 100644
index 000000000..bee5df323
--- /dev/null
+++ b/src/tsconfig.all.json
@@ -0,0 +1,16 @@
+{
+ "files": [],
+ "include": [],
+ "references": [
+ { "path": "." },
+ { "path": "./addons/attach" },
+ { "path": "./addons/fit" },
+ { "path": "./addons/fullscreen" },
+ { "path": "./addons/search" },
+ { "path": "./addons/terminado" },
+ { "path": "./addons/webLinks" },
+ { "path": "./addons/winptyCompat" },
+ { "path": "./addons/zmodem" }
+ ]
+}
+
\ No newline at end of file
diff --git a/tsconfig.json b/src/tsconfig.json
similarity index 53%
rename from tsconfig.json
rename to src/tsconfig.json
index 2d1d6e35f..0aa3abb88 100644
--- a/tsconfig.json
+++ b/src/tsconfig.json
@@ -1,7 +1,7 @@
{
+ "extends": "./tsconfig-base",
"compilerOptions": {
"module": "commonjs",
- "target": "es5",
"lib": [
"dom",
"es5",
@@ -9,19 +9,22 @@
"scripthost",
"es2015.promise"
],
- "rootDir": "src",
- "outDir": "lib",
- "sourceMap": true,
- "removeComments": true,
- "preserveWatchOutput": true,
+ "rootDir": ".",
+ "outDir": "../lib",
+
"noUnusedLocals": true,
"noImplicitAny": true
},
"include": [
- "src/**/*",
- "typings/xterm.d.ts"
+ "./**/*",
+ "../typings/xterm.d.ts"
],
"exclude": [
- "src/addons/**/*"
+ "./addons/**/*"
+ ],
+ "references": [
+ { "path": "./common" },
+ { "path": "./core" }
]
}
+
\ No newline at end of file
diff --git a/src/ui/MouseZoneManager.ts b/src/ui/MouseZoneManager.ts
index a232f5b94..372dccc57 100644
--- a/src/ui/MouseZoneManager.ts
+++ b/src/ui/MouseZoneManager.ts
@@ -23,11 +23,13 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
private _areZonesActive: boolean = false;
private _mouseMoveListener: (e: MouseEvent) => any;
+ private _mouseLeaveListener: (e: MouseEvent) => any;
private _clickListener: (e: MouseEvent) => any;
private _tooltipTimeout: number = null;
private _currentZone: IMouseZone = null;
private _lastHoverCoords: [number, number] = [null, null];
+ private _initialSelectionLength: number;
constructor(
private _terminal: ITerminal
@@ -38,6 +40,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
// These events are expensive, only listen to it when mouse zones are active
this._mouseMoveListener = e => this._onMouseMove(e);
+ this._mouseLeaveListener = e => this._onMouseLeave(e);
this._clickListener = e => this._onClick(e);
}
@@ -89,6 +92,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
if (!this._areZonesActive) {
this._areZonesActive = true;
this._terminal.element.addEventListener('mousemove', this._mouseMoveListener);
+ this._terminal.element.addEventListener('mouseleave', this._mouseLeaveListener);
this._terminal.element.addEventListener('click', this._clickListener);
}
}
@@ -97,6 +101,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
if (this._areZonesActive) {
this._areZonesActive = false;
this._terminal.element.removeEventListener('mousemove', this._mouseMoveListener);
+ this._terminal.element.removeEventListener('mouseleave', this._mouseLeaveListener);
this._terminal.element.removeEventListener('click', this._clickListener);
}
}
@@ -153,6 +158,10 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
}
private _onMouseDown(e: MouseEvent): void {
+ // Store current terminal selection length, to check if we're performing
+ // a selection operation
+ this._initialSelectionLength = this._terminal.getSelection().length;
+
// Ignore the event if there are no zones active
if (!this._areZonesActive) {
return;
@@ -169,10 +178,25 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
}
}
+ private _onMouseLeave(e: MouseEvent): void {
+ // Fire the hover end callback and cancel any existing timer if the mouse
+ // leaves the terminal element
+ if (this._currentZone) {
+ this._currentZone.leaveCallback();
+ this._currentZone = null;
+ if (this._tooltipTimeout) {
+ clearTimeout(this._tooltipTimeout);
+ }
+ }
+ }
+
private _onClick(e: MouseEvent): void {
- // Find the active zone and click it if found
+ // Find the active zone and click it if found and no selection was
+ // being performed
const zone = this._findZoneEventAt(e);
- if (zone) {
+ const currentSelectionLength = this._terminal.getSelection().length;
+
+ if (zone && currentSelectionLength === this._initialSelectionLength) {
zone.clickCallback(e);
e.preventDefault();
e.stopImmediatePropagation();
diff --git a/yarn.lock b/yarn.lock
index 5555321d1..440aa4f82 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1275,11 +1275,6 @@ combined-stream@1.0.6, combined-stream@~1.0.5:
dependencies:
delayed-stream "~1.0.0"
-commander@2.6.0:
- version "2.6.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d"
- integrity sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=
-
commander@2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
@@ -1338,21 +1333,6 @@ concat-with-sourcemaps@^1.0.0:
dependencies:
source-map "^0.6.1"
-concurrently@^3.5.1:
- version "3.6.0"
- resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.6.0.tgz#c25e34b156a9d5bd4f256a0d85f6192438ae481f"
- integrity sha512-6XiIYtYzmGEccNZFkih5JOH92jLA4ulZArAYy5j1uDSdrPLB3KzdE8GW7t2fHPcg9ry2+5LP9IEYzXzxw9lFdA==
- dependencies:
- chalk "^2.4.1"
- commander "2.6.0"
- date-fns "^1.23.0"
- lodash "^4.5.1"
- read-pkg "^3.0.0"
- rx "2.3.24"
- spawn-command "^0.0.2-1"
- supports-color "^3.2.3"
- tree-kill "^1.1.0"
-
configstore@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/configstore/-/configstore-1.4.0.tgz#c35781d0501d268c25c54b8b17f6240e8a4fb021"
@@ -1595,11 +1575,6 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.0.0"
whatwg-url "^6.4.0"
-date-fns@^1.23.0:
- version "1.29.0"
- resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6"
- integrity sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==
-
date-now@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@@ -1933,7 +1908,7 @@ errno@^0.1.3, errno@~0.1.7:
dependencies:
prr "~1.0.1"
-error-ex@^1.2.0, error-ex@^1.3.1:
+error-ex@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
@@ -3625,7 +3600,7 @@ jsesc@^1.3.0:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s=
-json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
+json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
@@ -3815,16 +3790,6 @@ load-json-file@^1.0.0:
pinkie-promise "^2.0.0"
strip-bom "^2.0.0"
-load-json-file@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
- integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs=
- dependencies:
- graceful-fs "^4.1.2"
- parse-json "^4.0.0"
- pify "^3.0.0"
- strip-bom "^3.0.0"
-
loader-runner@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
@@ -4037,7 +4002,7 @@ lodash.templatesettings@^3.0.0:
lodash._reinterpolate "^3.0.0"
lodash.escape "^3.0.0"
-lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.5.1:
+lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.4:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==
@@ -4984,14 +4949,6 @@ parse-json@^2.2.0:
dependencies:
error-ex "^1.2.0"
-parse-json@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
- integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
- dependencies:
- error-ex "^1.3.1"
- json-parse-better-errors "^1.0.1"
-
parse-passwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
@@ -5092,13 +5049,6 @@ path-type@^1.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
-path-type@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
- integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==
- dependencies:
- pify "^3.0.0"
-
pause-stream@0.0.11:
version "0.0.11"
resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
@@ -5384,15 +5334,6 @@ read-pkg@^1.0.0:
normalize-package-data "^2.3.2"
path-type "^1.0.0"
-read-pkg@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
- integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=
- dependencies:
- load-json-file "^4.0.0"
- normalize-package-data "^2.3.2"
- path-type "^3.0.0"
-
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
@@ -5688,11 +5629,6 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
-rx@2.3.24:
- version "2.3.24"
- resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7"
- integrity sha1-FPlQpCF9fjXapxu8vljv9o6ksrc=
-
rxjs@^6.1.0:
version "6.3.1"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.1.tgz#878a1a8c64b8a5da11dcf74b5033fe944cdafb84"
@@ -6025,11 +5961,6 @@ sparkles@^1.0.0:
resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c"
integrity sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==
-spawn-command@^0.0.2-1:
- version "0.0.2-1"
- resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
- integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=
-
spawn-wrap@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.4.2.tgz#cff58e73a8224617b6561abdc32586ea0c82248c"
@@ -6271,11 +6202,6 @@ strip-bom@^1.0.0:
first-chunk-stream "^1.0.0"
is-utf8 "^0.2.0"
-strip-bom@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
- integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
-
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@@ -6305,7 +6231,7 @@ supports-color@^2.0.0:
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
-supports-color@^3.1.2, supports-color@^3.2.3:
+supports-color@^3.1.2:
version "3.2.3"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=
@@ -6521,11 +6447,6 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
-tree-kill@^1.1.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36"
- integrity sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg==
-
trim-right@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
@@ -6633,10 +6554,10 @@ typedarray@^0.0.6, typedarray@~0.0.5:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-typescript@3.1:
- version "3.1.6"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68"
- integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==
+typescript@3.4:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.1.tgz#b6691be11a881ffa9a05765a205cb7383f3b63c6"
+ integrity sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==
uglify-es@^3.3.4:
version "3.3.9"