From 700a10f3f4a6a840fcdd55fec668f5cfd64a5712 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 27 Oct 2017 10:23:46 -0700 Subject: [PATCH 1/4] Initial winptyCompat addon --- demo/index.html | 1 + demo/main.js | 1 + gulpfile.js | 36 ++++++++++++++-- package.json | 2 +- src/addons/winptyCompat/tsconfig.json | 10 +++++ src/addons/winptyCompat/winptyCompat.ts | 56 +++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 src/addons/winptyCompat/tsconfig.json create mode 100644 src/addons/winptyCompat/winptyCompat.ts 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/gulpfile.js b/gulpfile.js index cf001da3..7e852cf2 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -19,7 +19,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; @@ -46,16 +45,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); }); /** @@ -105,6 +117,22 @@ gulp.task('browserify-addons', ['tsc'], function() { .pipe(sourcemaps.write('./')) .pipe(gulp.dest(buildDir)); + 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 @@ -114,7 +142,7 @@ gulp.task('browserify-addons', ['tsc'], function() { `!${outDir}/addons/search/**` ]).pipe(gulp.dest(`${buildDir}/addons`)); - return merge(searchBundle, copyAddons); + return merge(searchBundle, winptyCompatBundle, copyAddons); }); gulp.task('instrument-test', function () { diff --git a/package.json b/package.json index f32918df..91860da5 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "jsdoc": "3.4.3", "jsdom": "^11.1.0", "merge-stream": "^1.0.1", - "node-pty": "^0.4.1", + "node-pty": "^0.6.0", "nodemon": "1.10.2", "sorcery": "^0.10.0", "tslint": "^4.0.2", 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..06a2a81a --- /dev/null +++ b/src/addons/winptyCompat/winptyCompat.ts @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +declare var exports: any; +declare var module: any; +declare var define: any; +declare var require: any; +declare var window: 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[line.length - 1]; + if (lastChar[3] !== 32 /* ' ' */) { + const nextLine = this.buffer.lines.get(this.buffer.ybase + this.buffer.y); + (nextLine).isWrapped = true; + } + }); + }; +}); From 2fbc0be98d3f730a68898cc244ab439aba5a240f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 29 Oct 2017 12:45:37 -0700 Subject: [PATCH 2/4] Fix build issues --- package.json | 2 +- src/addons/winptyCompat/winptyCompat.ts | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/package.json b/package.json index 91860da5..8d337fc8 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "jsdoc": "3.4.3", "jsdom": "^11.1.0", "merge-stream": "^1.0.1", - "node-pty": "^0.6.0", + "node-pty": "^0.7.2", "nodemon": "1.10.2", "sorcery": "^0.10.0", "tslint": "^4.0.2", diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts index 06a2a81a..1c289c70 100644 --- a/src/addons/winptyCompat/winptyCompat.ts +++ b/src/addons/winptyCompat/winptyCompat.ts @@ -4,10 +4,7 @@ */ declare var exports: any; -declare var module: any; declare var define: any; -declare var require: any; -declare var window: any; (function (addon) { if (typeof window !== 'undefined' && 'Terminal' in window) { From d7f6f96ba678887c2100e8a4047a1a72f4d45957 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 29 Oct 2017 13:01:38 -0700 Subject: [PATCH 3/4] Add winptyCompat addon typings --- fixtures/typings-test/typings-test.ts | 1 + typings/xterm.d.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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/typings/xterm.d.ts b/typings/xterm.d.ts index f20f3502..0997debf 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -513,6 +513,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; } } From aa5a9911de92e87a262ebad475c8df976d567da4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 29 Oct 2017 13:16:29 -0700 Subject: [PATCH 4/4] Base wrapped line on current width, not original width Lines are never trimmed, so even if the term's size is reduced it could contain empty lines at the end when wrapping is valid --- src/addons/winptyCompat/winptyCompat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts index 1c289c70..7474df2e 100644 --- a/src/addons/winptyCompat/winptyCompat.ts +++ b/src/addons/winptyCompat/winptyCompat.ts @@ -43,7 +43,7 @@ declare var define: any; // wrapped. this.on('lineFeed', () => { const line = this.buffer.lines.get(this.buffer.ybase + this.buffer.y - 1); - const lastChar = line[line.length - 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;