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 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/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/typings/xterm.d.ts b/typings/xterm.d.ts index f0ab9daf..19f2b784 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; } }