diff --git a/Dockerfile b/Dockerfile index 36e821bd..1f0db1f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,6 @@ FROM node:6.9 MAINTAINER Paris Kasidiaris -# Install cpio, used for building -RUN apt-get update \ - && apt-get install -y --no-install-recommends cpio \ - && rm -rf /var/lib/apt/lists/* - # Set the working directory WORKDIR /usr/src/app diff --git a/bin/build b/bin/build deleted file mode 100755 index a01a1b9f..00000000 --- a/bin/build +++ /dev/null @@ -1,36 +0,0 @@ -#! /usr/bin/env bash - -set -e - -# $BUILD_DIR should default to "build" -BUILD_DIR=${BUILD_DIR:=build} - -# Create the build directory -mkdir -p $BUILD_DIR - - -# Clean lib/* to prevent confusion if files were deleted in src/ -rm -rf lib/* - -# Build all TypeScript files (including tests) to lib/ -tsc - -# Concat all xterm.js files into a single file and output as a UMD to $BUILD_DIR/xterm.js -browserify ./lib/xterm.js --standalone Terminal --debug --outfile ./$BUILD_DIR/xterm.js -cat ./$BUILD_DIR/xterm.js | exorcist ./$BUILD_DIR/xterm.js.map -b ./$BUILD_DIR > ./$BUILD_DIR/xterm.temp.js -rm ./$BUILD_DIR/xterm.js -mv ./$BUILD_DIR/xterm.temp.js ./$BUILD_DIR/xterm.js - -# Resolve the chain of sourcemaps so that ./$BUILD_DIR/xterm.js.map points at ./src -sorcery -i $BUILD_DIR/xterm.js - -# Copy all CSS files from src/ to $BUILD_DIR/ and lib/ -cd src -find . -name '*.css' | cpio -pdm ../$BUILD_DIR -find . -name '*.css' | cpio -pdm ../lib -cd .. - -# Copy addons from lib/ to $BUILD_DIR/ -cd lib/addons -find . -name '*.js' | cpio -pdm ../../$BUILD_DIR/addons -cd ../.. diff --git a/bin/prepare-release b/bin/prepare-release index 6bd39d3d..464e65b4 100755 --- a/bin/prepare-release +++ b/bin/prepare-release @@ -21,7 +21,7 @@ CURRENT_BOWER_JSON_VERSION=$(cat bower.json \ # Build xterm.js into `dist` export BUILD_DIR=dist -./bin/build +npm run build # Update AUTHORS file sh bin/generate-authors diff --git a/demo/app.js b/demo/app.js index a4122ace..3439ea99 100644 --- a/demo/app.js +++ b/demo/app.js @@ -2,7 +2,7 @@ var express = require('express'); var app = express(); var expressWs = require('express-ws')(app); var os = require('os'); -var pty = require('pty.js'); +var pty = require('node-pty'); var terminals = {}, logs = {}; diff --git a/demo/index.html b/demo/index.html index 285fe002..764c8442 100644 --- a/demo/index.html +++ b/demo/index.html @@ -16,7 +16,12 @@

Options

- +

+ +

+

+ +

Size

diff --git a/demo/main.js b/demo/main.js index 86fbd314..078b5c8d 100644 --- a/demo/main.js +++ b/demo/main.js @@ -8,7 +8,8 @@ var term, var terminalContainer = document.getElementById('terminal-container'), optionElements = { - cursorBlink: document.querySelector('#option-cursor-blink') + cursorBlink: document.querySelector('#option-cursor-blink'), + scrollback: document.querySelector('#option-scrollback') }, colsElement = document.getElementById('cols'), rowsElement = document.getElementById('rows'); @@ -27,7 +28,12 @@ function setTerminalSize () { colsElement.addEventListener('change', setTerminalSize); rowsElement.addEventListener('change', setTerminalSize); -optionElements.cursorBlink.addEventListener('change', createTerminal); +optionElements.cursorBlink.addEventListener('change', function () { + term.setOption('cursorBlink', optionElements.cursorBlink.checked); +}); +optionElements.scrollback.addEventListener('change', function () { + terminal.setOption('scrollback', parseInt(optionElements.scrollback.value, 10)); +}); createTerminal(); @@ -37,7 +43,8 @@ function createTerminal() { terminalContainer.removeChild(terminalContainer.children[0]); } term = new Terminal({ - cursorBlink: optionElements.cursorBlink.checked + cursorBlink: optionElements.cursorBlink.checked, + scrollback: parseInt(optionElements.scrollback.value, 10) }); term.on('resize', function (size) { if (!pid) { @@ -78,7 +85,6 @@ function createTerminal() { }); } - function runRealTerminal() { term.attach(socket); term._initialized = true; diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 00000000..144b8831 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,87 @@ +const browserify = require('browserify'); +const buffer = require('vinyl-buffer'); +const fs = require('fs-extra'); +const gulp = require('gulp'); +const merge = require('merge-stream'); +const sorcery = require('sorcery'); +const source = require('vinyl-source-stream'); +const sourcemaps = require('gulp-sourcemaps'); +const ts = require('gulp-typescript'); +const tsify = require('tsify'); + + +let buildDir = process.env.BUILD_DIR || 'build'; + + +/** + * Compile TypeScript sources to JavaScript files and create a source map file for each TypeScript + * file compiled. + */ +gulp.task('tsc', function () { + // Remove the lib/ directory to prevent confusion if files were deleted in src/ + fs.emptyDirSync('lib'); + + // Build all TypeScript files (including tests) to lib/, based on the configuration defined in + // `tsconfig.json`. + let tsProject = ts.createProject('tsconfig.json'); + let tsResult = tsProject.src().pipe(sourcemaps.init()).pipe(tsProject()); + let tsc = tsResult.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest('lib')); + + // Copy all addons from src/ to lib/ + let copyAddons = gulp.src('src/addons/**/*').pipe(gulp.dest('lib/addons')); + + // Copy stylesheets from src/ to lib/ + let copyStylesheets = gulp.src('src/**/*.css').pipe(gulp.dest('lib')); + + return merge(tsc, copyAddons, copyStylesheets); +}); + +/** + * Bundle JavaScript files produced by the `tsc` task, into a single file named `xterm.js` with + * Browserify. + */ +gulp.task('browserify', ['tsc'], function() { + // Ensure that the build directory exists + fs.ensureDirSync(buildDir); + + let browserifyOptions = { + basedir: buildDir, + debug: true, + entries: ['../lib/xterm.js'], + standalone: 'Terminal', + cache: {}, + packageCache: {} + }; + let bundleStream = browserify(browserifyOptions) + .plugin(tsify) + .bundle() + .pipe(source('xterm.js')) + .pipe(buffer()) + .pipe(sourcemaps.init({loadMaps: true, sourceRoot: '..'})) + .pipe(sourcemaps.write('./')) + .pipe(gulp.dest(buildDir)); + + // Copy all add-ons from lib/ to buildDir + let copyAddons = gulp.src('lib/addons/**/*').pipe(gulp.dest(`${buildDir}/addons`)); + + // Copy stylesheets from src/ to lib/ + let copyStylesheets = gulp.src('lib/**/*.css').pipe(gulp.dest(buildDir)); + + return merge(bundleStream, copyAddons, copyStylesheets); +}); + + +/** + * Use `sorcery` to resolve the source map chain and point back to the TypeScript files. + * (Without this task the source maps produced for the JavaScript bundle points into the + * compiled JavaScript files in lib/). + */ +gulp.task('sorcery', ['browserify'], function () { + var chain = sorcery.loadSync(`${buildDir}/xterm.js`); + var map = chain.apply(); + chain.writeSync(); +}); + +gulp.task('build', ['sorcery']); + +gulp.task('default', ['build']); diff --git a/package.json b/package.json index 734bb6fb..67eb824d 100644 --- a/package.json +++ b/package.json @@ -38,18 +38,26 @@ "browserify": "^13.1.0", "chai": "3.5.0", "docdash": "0.4.0", - "exorcist": "^0.4.0", "express": "4.13.4", "express-ws": "2.0.0-rc.1", + "fs-extra": "^1.0.0", "glob": "^7.0.5", + "gulp": "^3.9.1", + "gulp-cli": "^1.2.2", + "gulp-sourcemaps": "1.9.1", + "gulp-typescript": "^3.1.3", "jsdoc": "3.4.3", + "merge-stream": "^1.0.1", "mocha": "2.5.3", + "node-pty": "^0.4.1", "nodemon": "1.10.2", - "pty.js": "0.3.1", "sleep": "^3.0.1", "sorcery": "^0.10.0", + "tsify": "^3.0.0", "tslint": "^4.0.2", - "typescript": "^2.0.3" + "typescript": "^2.0.3", + "vinyl-buffer": "^1.0.0", + "vinyl-source-stream": "^1.1.0" }, "scripts": { "prestart": "npm run build", @@ -58,7 +66,7 @@ "lint": "tslint src/**/*.ts", "test": "mocha --recursive ./lib", "build:docs": "jsdoc -c jsdoc.json", - "build": "./bin/build", + "build": "gulp build", "prepublish": "npm run build" } } diff --git a/src/test/escape-sequences-test.js b/src/test/escape-sequences-test.js index e9183ccf..6c64b562 100644 --- a/src/test/escape-sequences-test.js +++ b/src/test/escape-sequences-test.js @@ -1,6 +1,6 @@ var glob = require('glob'); var fs = require('fs'); -var pty = require('pty.js'); +var pty = require('node-pty'); var sleep = require('sleep'); var Terminal = require('../xterm'); diff --git a/src/test/test.js b/src/test/test.js index 7c716b2a..4f9342e5 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -23,6 +23,11 @@ describe('xterm.js', function() { xterm.writeBuffer.push(data); xterm.innerWrite(); }; + xterm.element = { + classList: { + toggle: function(){} + } + }; }); describe('getOption', function() { diff --git a/src/xterm.css b/src/xterm.css index 3f4f4bbe..4ef61fdf 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -82,11 +82,11 @@ background-color: transparent; } -.terminal.focus .terminal-cursor.blinking { - animation: blink-cursor 1.2s infinite step-end; +.terminal.focus.xterm-cursor-blink .terminal-cursor { + animation: cursor-blink 1.2s infinite step-end; } -@keyframes blink-cursor { +@keyframes cursor-blink { 0% { background-color: #fff; color: #000; diff --git a/src/xterm.js b/src/xterm.js index b3ecdf8d..40de418a 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -10,15 +10,15 @@ * @license MIT */ -import { CompositionHelper } from './CompositionHelper.js'; -import { EventEmitter } from './EventEmitter.js'; -import { Viewport } from './Viewport.js'; -import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard.js'; -import { CircularList } from './utils/CircularList.js'; +import { CompositionHelper } from './CompositionHelper'; +import { EventEmitter } from './EventEmitter'; +import { Viewport } from './Viewport'; +import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard'; +import { CircularList } from './utils/CircularList'; import { C0 } from './EscapeSequences'; import { InputHandler } from './InputHandler'; import { Parser } from './Parser'; -import { CharMeasure } from './utils/CharMeasure.js'; +import { CharMeasure } from './utils/CharMeasure'; import * as Browser from './utils/Browser'; import * as Keyboard from './utils/Keyboard'; import { CHARSETS } from './Charsets'; @@ -410,8 +410,29 @@ Terminal.prototype.setOption = function(key, value) { if (!(key in Terminal.defaults)) { throw new Error('No option with key "' + key + '"'); } + switch (key) { + case 'scrollback': + if (this.options[key] !== value) { + if (this.lines.length > value) { + const amountToTrim = this.lines.length - value; + const needsRefresh = (this.ydisp - amountToTrim < 0); + this.lines.trimStart(amountToTrim); + this.ybase = Math.max(this.ybase - amountToTrim, 0); + this.ydisp = Math.max(this.ydisp - amountToTrim, 0); + if (needsRefresh) { + this.refresh(0, this.rows - 1); + } + } + this.lines.maxLength = value; + this.viewport.syncScrollArea(); + } + break; + } this[key] = value; this.options[key] = value; + switch (key) { + case 'cursorBlink': this.element.classList.toggle('xterm-cursor-blink', value); break; + } }; /** @@ -509,7 +530,11 @@ Terminal.bindKeys = function(term) { term.keyPress(ev); }, true); - on(term.element, 'keyup', term.focus.bind(term)); + on(term.element, 'keyup', function(ev) { + if (!wasMondifierKeyOnlyEvent(ev)) { + term.focus(term); + } + }, true); on(term.textarea, 'keydown', function(ev) { term.keyDown(ev); @@ -568,6 +593,7 @@ Terminal.prototype.open = function(parent) { this.element.classList.add('terminal'); this.element.classList.add('xterm'); this.element.classList.add('xterm-theme-' + this.theme); + this.element.classList.toggle('xterm-cursor-blink', this.options.cursorBlink); this.element.style.height this.element.setAttribute('tabindex', 0); @@ -619,7 +645,7 @@ Terminal.prototype.open = function(parent) { } this.parent.appendChild(this.element); - this.charMeasure = new CharMeasure(this.rowContainer); + this.charMeasure = new CharMeasure(this.helperContainer); this.charMeasure.on('charsizechanged', function () { self.updateCharSizeCSS(); }); @@ -1144,6 +1170,10 @@ Terminal.prototype.refresh = function(start, end) { row = y + this.ydisp; line = this.lines.get(row); + if (!line || !this.children[y]) { + // Continue if the line is not available, this means a resize is currently in progress + continue; + } out = ''; if (this.y === y - (this.ybase - this.ydisp) @@ -1158,6 +1188,10 @@ Terminal.prototype.refresh = function(start, end) { i = 0; for (; i < width; i++) { + if (!line[i]) { + // Continue if the character is not available, this means a resize is currently in progress + continue; + } data = line[i][0]; ch = line[i][1]; ch_width = line[i][2]; @@ -1172,11 +1206,7 @@ Terminal.prototype.refresh = function(start, end) { } if (data !== this.defAttr) { if (data === -1) { - out += ''; + out += ''; } else { var classNames = []; @@ -2441,6 +2471,12 @@ function each(obj, iter, con) { } } +function wasMondifierKeyOnlyEvent(ev) { + return ev.keyCode === 16 || // Shift + ev.keyCode === 17 || // Ctrl + ev.keyCode === 18; // Alt +} + function keys(obj) { if (Object.keys) return Object.keys(obj); var key, keys = []; diff --git a/tsconfig.json b/tsconfig.json index f4a5a1b2..f5a7d66c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,13 +7,17 @@ "outDir": "lib", "sourceMap": true }, + "include": [ + "src/**/*" + ], "exclude": [ - "addons", + "src/addons/**/*", "build", "demo", "dist", "out", "test", - "node_modules" + "node_modules", + "docs" ] }