diff --git a/demo/index.html b/demo/index.html
index daa60b6f..8d900903 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -12,6 +12,7 @@
+
xterm.js: xterm, in the browser
diff --git a/demo/main.js b/demo/main.js
index 2ed2b33c..9de682bf 100644
--- a/demo/main.js
+++ b/demo/main.js
@@ -91,6 +91,7 @@ function createTerminal() {
term.open(terminalContainer);
term.fit();
+ term.winptyCompatInit();
// fit is called within a setTimeout, cols and rows need this.
setTimeout(function () {
diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts
index c0ac8b50..be362395 100644
--- a/fixtures/typings-test/typings-test.ts
+++ b/fixtures/typings-test/typings-test.ts
@@ -41,6 +41,7 @@ namespace static_methods {
Terminal.loadAddon('fullscreen');
Terminal.loadAddon('search');
Terminal.loadAddon('terminado');
+ Terminal.loadAddon('winptyCompat');
}
}
diff --git a/gulpfile.js b/gulpfile.js
index 31beba63..25821349 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -20,7 +20,6 @@ const util = require('gulp-util');
let buildDir = process.env.BUILD_DIR || 'build';
let tsProject = ts.createProject('tsconfig.json');
-let tsProjectSearchAddon = ts.createProject('./src/addons/search/tsconfig.json');
let srcDir = tsProject.config.compilerOptions.rootDir;
let outDir = tsProject.config.compilerOptions.outDir;
@@ -47,16 +46,29 @@ gulp.task('tsc', function () {
);
fs.emptyDirSync(`${outDir}/addons/search`);
+ fs.emptyDirSync(`${outDir}/addons/winptyCompat`);
+
+ let tsProjectSearchAddon = ts.createProject('./src/addons/search/tsconfig.json');
let tsResultSearchAddon = tsProjectSearchAddon.src().pipe(sourcemaps.init()).pipe(tsProjectSearchAddon());
let tscSearchAddon = tsResultSearchAddon.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(`${outDir}/addons/search`));
+ let tsProjectWinptyCompatAddon = ts.createProject('./src/addons/winptyCompat/tsconfig.json');
+ let tsResultWinptyCompatAddon = tsProjectWinptyCompatAddon.src().pipe(sourcemaps.init()).pipe(tsProjectWinptyCompatAddon());
+ let tscWinptyCompatAddon = tsResultWinptyCompatAddon.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(`${outDir}/addons/winptyCompat`));
+
// Copy all addons from ${srcDir}/ to ${outDir}/
- let copyAddons = gulp.src([`${srcDir}/addons/**/*`, `!${srcDir}/addons/search`, `!${srcDir}/addons/search/**`]).pipe(gulp.dest(`${outDir}/addons`));
+ let copyAddons = gulp.src([
+ `${srcDir}/addons/**/*`,
+ `!${srcDir}/addons/search`,
+ `!${srcDir}/addons/search/**`,
+ `!${srcDir}/addons/winptyCompat`,
+ `!${srcDir}/addons/winptyCompat/**`
+ ]).pipe(gulp.dest(`${outDir}/addons`));
// Copy stylesheets from ${srcDir}/ to ${outDir}/
let copyStylesheets = gulp.src(`${srcDir}/**/*.css`).pipe(gulp.dest(outDir));
- return merge(tsc, tscSearchAddon, copyAddons, copyStylesheets);
+ return merge(tsc, tscSearchAddon, tscWinptyCompatAddon, copyAddons, copyStylesheets);
});
/**
@@ -116,6 +128,22 @@ gulp.task('browserify-addons', ['tsc'], function() {
`${outDir}/addons/zmodem/demo/**`,
]).pipe(gulp.dest(`${buildDir}/addons/zmodem/demo`));
+ let winptyCompatOptions = {
+ basedir: `${buildDir}/addons/winptyCompat`,
+ debug: true,
+ entries: [`${outDir}/addons/winptyCompat/winptyCompat.js`],
+ cache: {},
+ packageCache: {}
+ };
+ let winptyCompatBundle = browserify(winptyCompatOptions)
+ .external(path.join(outDir, 'Terminal.js'))
+ .bundle()
+ .pipe(source('./addons/winptyCompat/winptyCompat.js'))
+ .pipe(buffer())
+ .pipe(sourcemaps.init({loadMaps: true, sourceRoot: ''}))
+ .pipe(sourcemaps.write('./'))
+ .pipe(gulp.dest(buildDir));
+
// Copy all add-ons from outDir to buildDir
let copyAddons = gulp.src([
// Copy JS addons
@@ -126,7 +154,7 @@ gulp.task('browserify-addons', ['tsc'], function() {
`!${outDir}/addons/zmodem/**`,
]).pipe(gulp.dest(`${buildDir}/addons`));
- return merge(searchBundle, zmodemBundle, zmodemDemoBundle, copyAddons);
+ return merge(searchBundle, zmodemBundle, zmodemDemoBundle, winptyCompatBundle, copyAddons);
});
gulp.task('instrument-test', function () {
diff --git a/package.json b/package.json
index d08862ac..b308967b 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,7 @@
"jsdoc": "3.4.3",
"jsdom": "^11.1.0",
"merge-stream": "^1.0.1",
- "node-pty": "0.7.2",
+ "node-pty": "^0.7.2",
"nodemon": "1.10.2",
"sorcery": "^0.10.0",
"tslint": "^4.0.2",
diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts
index 5dd652e0..4f50d35a 100644
--- a/src/SelectionManager.ts
+++ b/src/SelectionManager.ts
@@ -307,6 +307,15 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1));
}
+ /**
+ * Returns whether the selection manager should force selection, regardless of
+ * whether the terminal is in mouse events mode.
+ * @param event The mouse event.
+ */
+ public shouldForceSelection(event: MouseEvent): boolean {
+ return Browser.isMac ? event.altKey : event.shiftKey;
+ }
+
/**
* Handles te mousedown event, setting up for a new selection.
* @param event The mousedown event.
@@ -325,9 +334,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// Allow selection when using a specific modifier key, even when disabled
if (!this._enabled) {
- const shouldForceSelection = Browser.isMac ? event.altKey : event.shiftKey;
-
- if (!shouldForceSelection) {
+ if (!this.shouldForceSelection(event)) {
return;
}
diff --git a/src/Terminal.ts b/src/Terminal.ts
index 394fb4de..b948f72c 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -943,7 +943,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
ev.preventDefault();
this.focus();
- if (!this.mouseEvents) return;
+ // Don't send the mouse button to the pty if mouse events are disabled or
+ // if the selection manager is having selection forced (ie. a modifier is
+ // held).
+ if (!this.mouseEvents || this.selectionManager.shouldForceSelection(ev)) {
+ return;
+ }
// send the button
sendButton(ev);
@@ -1417,6 +1422,36 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
};
const modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0);
switch (ev.keyCode) {
+ case 0:
+ if (ev.key === 'UIKeyInputUpArrow') {
+ if (this.applicationCursor) {
+ result.key = C0.ESC + 'OA';
+ } else {
+ result.key = C0.ESC + '[A';
+ }
+ }
+ else if (ev.key === 'UIKeyInputLeftArrow') {
+ if (this.applicationCursor) {
+ result.key = C0.ESC + 'OD';
+ } else {
+ result.key = C0.ESC + '[D';
+ }
+ }
+ else if (ev.key === 'UIKeyInputRightArrow') {
+ if (this.applicationCursor) {
+ result.key = C0.ESC + 'OC';
+ } else {
+ result.key = C0.ESC + '[C';
+ }
+ }
+ else if (ev.key === 'UIKeyInputDownArrow') {
+ if (this.applicationCursor) {
+ result.key = C0.ESC + 'OB';
+ } else {
+ result.key = C0.ESC + '[B';
+ }
+ }
+ break;
case 8:
// backspace
if (ev.shiftKey) {
diff --git a/src/addons/winptyCompat/tsconfig.json b/src/addons/winptyCompat/tsconfig.json
new file mode 100644
index 00000000..e15a09a8
--- /dev/null
+++ b/src/addons/winptyCompat/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "rootDir": ".",
+ "outDir": "../../../lib/addons/winptyCompat/",
+ "sourceMap": true,
+ "removeComments": true
+ }
+}
diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts
new file mode 100644
index 00000000..7474df2e
--- /dev/null
+++ b/src/addons/winptyCompat/winptyCompat.ts
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+declare var exports: any;
+declare var define: any;
+
+(function (addon) {
+ if (typeof window !== 'undefined' && 'Terminal' in window) {
+ /**
+ * Plain browser environment
+ */
+ addon((window).Terminal);
+ } else if (typeof exports === 'object' && typeof module === 'object') {
+ /**
+ * CommonJS environment
+ */
+ module.exports = addon(require('../../Terminal').Terminal);
+ } else if (typeof define === 'function') {
+ /**
+ * Require.js is available
+ */
+ define(['../../xterm'], addon);
+ }
+})((Terminal: any) => {
+ Terminal.prototype.winptyCompatInit = function(): void {
+ // Don't do anything when the platform is not Windows
+ const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
+ if (!isWindows) {
+ return;
+ }
+
+ // 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
+ // retaining the wrapped new line characters or if consumers are listening
+ // in on the data stream.
+ //
+ // The workaround for this is to listen to every incoming line feed and mark
+ // the line as wrapped if the last character in the previous line is not a
+ // space. This is certainly not without its problems, but generally on
+ // Windows when text reaches the end of the terminal it's likely going to be
+ // wrapped.
+ this.on('lineFeed', () => {
+ const line = this.buffer.lines.get(this.buffer.ybase + this.buffer.y - 1);
+ const lastChar = line[this.cols - 1];
+ if (lastChar[3] !== 32 /* ' ' */) {
+ const nextLine = this.buffer.lines.get(this.buffer.ybase + this.buffer.y);
+ (nextLine).isWrapped = true;
+ }
+ });
+ };
+});
diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts
index 5559d270..b4becbce 100644
--- a/src/renderer/BaseRenderLayer.ts
+++ b/src/renderer/BaseRenderLayer.ts
@@ -15,12 +15,12 @@ const DIM_OPACITY = 0.5;
export abstract class BaseRenderLayer implements IRenderLayer {
private _canvas: HTMLCanvasElement;
protected _ctx: CanvasRenderingContext2D;
- private _scaledCharWidth: number;
- private _scaledCharHeight: number;
- private _scaledCellWidth: number;
- private _scaledCellHeight: number;
- private _scaledCharLeft: number;
- private _scaledCharTop: number;
+ private _scaledCharWidth: number = 0;
+ private _scaledCharHeight: number = 0;
+ private _scaledCellWidth: number = 0;
+ private _scaledCellHeight: number = 0;
+ private _scaledCharLeft: number = 0;
+ private _scaledCharTop: number = 0;
private _charAtlas: HTMLCanvasElement | ImageBitmap;
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index f0ab9daf..10bd255f 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -290,7 +290,9 @@ declare module 'xterm' {
/**
* Opens the terminal within an element.
- * @param parent The element to create the terminal within.
+ * @param parent The element to create the terminal within. This element
+ * must be visible (have dimensions) when `open` is called as several DOM-
+ * based measurements need to be performed when this function is called.
*/
open(parent: HTMLElement): void;
@@ -513,6 +515,6 @@ declare module 'xterm' {
* available to all newly created Terminals.
* @param addon The addon to load.
*/
- static loadAddon(addon: 'attach' | 'fit' | 'fullscreen' | 'search' | 'terminado'): void;
+ static loadAddon(addon: 'attach' | 'fit' | 'fullscreen' | 'search' | 'terminado' | 'winptyCompat'): void;
}
}