From c63732ef5f0bdac18b395a482ffed6c92e689a66 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 12 Nov 2016 14:24:57 -0800 Subject: [PATCH 1/6] Set up source maps Fixes #354 --- bin/build | 8 +++++++- package.json | 3 ++- tsconfig.json | 3 ++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/bin/build b/bin/build index 00a73236..d1b403d2 100755 --- a/bin/build +++ b/bin/build @@ -4,7 +4,13 @@ tsc # Concat all xterm.js files into a single file and output as a UMD to dist/xterm.js -browserify out/xterm.js --standalone Terminal -p [ tsify ] --outfile dist/xterm.js +browserify ./out/xterm.js --standalone Terminal --debug --outfile ./dist/xterm.js +cat ./dist/xterm.js | exorcist ./dist/xterm.js.map -b ./dist > ./dist/xterm.temp.js +rm ./dist/xterm.js +mv ./dist/xterm.temp.js ./dist/xterm.js + +# Resolve the chain of sourcemaps so that ./dist/xterm.js.map points at ./src +sorcery -i dist/xterm.js # Copy all CSS files from src/ to dist/ cd src diff --git a/package.json b/package.json index 93ed9a7b..72c27e7d 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "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", "glob": "^7.0.5", @@ -22,7 +23,7 @@ "nodemon": "1.10.2", "pty.js": "0.3.1", "sleep": "^3.0.1", - "tsify": "^1.0.7", + "sorcery": "^0.10.0", "typescript": "^2.0.3" }, "scripts": { diff --git a/tsconfig.json b/tsconfig.json index beb83261..01e72bc1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,8 @@ "target": "es5", "rootDir": "src", "allowJs": true, - "outDir": "out" + "outDir": "out", + "sourceMap": true }, "exclude": [ "addons", From 9937d544101e958348690c8176e455c876fcbd40 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 21 Nov 2016 13:59:30 -0800 Subject: [PATCH 2/6] Prevent keyboard modifier events from scrolling down Fixes #363 --- package.json | 2 ++ src/test/test.js | 2 +- src/utils/Keyboard.test.ts | 21 +++++++++++++++++++++ src/utils/Keyboard.ts | 22 ++++++++++++++++++++++ src/xterm.js | 3 ++- 5 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/utils/Keyboard.test.ts create mode 100644 src/utils/Keyboard.ts diff --git a/package.json b/package.json index 93ed9a7b..519b7400 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "repository": "https://github.com/sourcelair/xterm.js", "license": "MIT", "devDependencies": { + "@types/chai": "^3.4.34", + "@types/mocha": "^2.2.33", "@types/node": "^6.0.41", "browserify": "^13.1.0", "chai": "3.5.0", diff --git a/src/test/test.js b/src/test/test.js index 08593e12..6f700002 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -194,7 +194,7 @@ describe('xterm.js', function() { terminal.ydisp = 0; terminal.ybase = 40; - terminal.keyDown(); + terminal.keyDown({ keyCode: 0 }); // Ensure that now the terminal is scrolled to bottom assert.equal(terminal.ydisp, terminal.ybase); diff --git a/src/utils/Keyboard.test.ts b/src/utils/Keyboard.test.ts new file mode 100644 index 00000000..618d3331 --- /dev/null +++ b/src/utils/Keyboard.test.ts @@ -0,0 +1,21 @@ +import { assert } from 'chai'; +import * as Keyboard from './Keyboard'; + +describe('Keyboard', () => { + describe('isModifierOnlyKeyboardEvent', () => { + it('should return true when only modifier keys are used', () => { + // Note that KeyboardEvent.keyCode is deprecated but we're using it to improve browser + // compatibility. This helper returns the `any` type because KeyboardEvent doesn't exist under + // NodeJS. + function createEvent(keyCode: number): any { + return { keyCode }; + } + assert.isTrue(Keyboard.isModifierOnlyKeyboardEvent(createEvent(16))); + assert.isTrue(Keyboard.isModifierOnlyKeyboardEvent(createEvent(17))); + assert.isTrue(Keyboard.isModifierOnlyKeyboardEvent(createEvent(18))); + assert.isTrue(Keyboard.isModifierOnlyKeyboardEvent(createEvent(91))); + assert.isFalse(Keyboard.isModifierOnlyKeyboardEvent(createEvent(19))); + assert.isFalse(Keyboard.isModifierOnlyKeyboardEvent(createEvent(90))); + }); + }); +}); diff --git a/src/utils/Keyboard.ts b/src/utils/Keyboard.ts new file mode 100644 index 00000000..e43cba45 --- /dev/null +++ b/src/utils/Keyboard.ts @@ -0,0 +1,22 @@ +/** + * xterm.js: xterm, in the browser + * Copyright (c) 2016, SourceLair Private Company (MIT License) + */ + +/** + * Keyboard utilities module. This module contains utilities for dealing with keyboard interaction. + * @module xterm/utils/Keyboard + */ + +/** + * Gets whether a KeyboardEvent is made up entirely of modifier keys. + * + * @param event The event to check. + * @return Whether the KeyboardEvent is made up entirely of modifier keys. + */ +export function isModifierOnlyKeyboardEvent(event: KeyboardEvent): boolean { + return event.keyCode === 16 || // Shift + event.keyCode === 17 || // Control + event.keyCode === 18 || // Alt + event.keyCode === 91; // Meta +} diff --git a/src/xterm.js b/src/xterm.js index 85efc223..5924b796 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -36,6 +36,7 @@ import { EventEmitter } from './EventEmitter.js'; import { Viewport } from './Viewport.js'; import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard.js'; import * as Browser from './utils/Browser'; +import * as Keyboard from './utils/Keyboard'; /** * Terminal Emulation References: @@ -2426,7 +2427,7 @@ Terminal.prototype.attachCustomKeydownHandler = function(customKeydownHandler) { */ Terminal.prototype.keyDown = function(ev) { // Scroll down to prompt, whenever the user presses a key. - if (this.ybase !== this.ydisp) { + if (!Keyboard.isModifierOnlyKeyboardEvent(ev) && this.ybase !== this.ydisp) { this.scrollToBottom(); } From 634d4af4dddf02845b5faa76316edcffaae7def2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 21 Nov 2016 14:27:41 -0800 Subject: [PATCH 3/6] Rename Viewport.js -> Viewport.ts Part of #335 --- src/{Viewport.js => Viewport.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{Viewport.js => Viewport.ts} (100%) diff --git a/src/Viewport.js b/src/Viewport.ts similarity index 100% rename from src/Viewport.js rename to src/Viewport.ts From e0d98711956bb1a78110aa98dec10b046a08a7f4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 21 Nov 2016 14:39:08 -0800 Subject: [PATCH 4/6] Convert Viewport to a TS class --- src/Interfaces.ts | 14 +++ src/Viewport.ts | 211 ++++++++++++++++++++++++---------------------- 2 files changed, 123 insertions(+), 102 deletions(-) create mode 100644 src/Interfaces.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts new file mode 100644 index 00000000..14355e57 --- /dev/null +++ b/src/Interfaces.ts @@ -0,0 +1,14 @@ +/** + * xterm.js: xterm, in the browser + * Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License) + */ + +export interface ITerminal { + rowContainer: HTMLElement; + ydisp: number; + lines: string[]; + rows: number; + + on(event: string, callback: () => void); + scrollDisp(disp: number, suppressScrollEvent: boolean); +} diff --git a/src/Viewport.ts b/src/Viewport.ts index b47e8053..573a050c 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -3,112 +3,119 @@ * Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License) */ +import { ITerminal } from './Interfaces'; + /** * Represents the viewport of a terminal, the visible area within the larger buffer of output. * Logic for the virtual scroll bar is included in this object. - * @param {Terminal} terminal The Terminal object. - * @param {HTMLElement} viewportElement The DOM element acting as the viewport - * @param {HTMLElement} charMeasureElement A DOM element used to measure the character size of - * the terminal. + * @param viewportElement The DOM element acting as the viewport. + * @param scrollArea The DOM element acting as the scroll area. + * @param charMeasureElement A DOM element used to measure the character size of. the terminal. */ -function Viewport(terminal, viewportElement, scrollArea, charMeasureElement) { - this.terminal = terminal; - this.viewportElement = viewportElement; - this.scrollArea = scrollArea; - this.charMeasureElement = charMeasureElement; - this.currentRowHeight = 0; - this.lastRecordedBufferLength = 0; - this.lastRecordedViewportHeight = 0; +export class Viewport { - this.terminal.on('scroll', this.syncScrollArea.bind(this)); - this.terminal.on('resize', this.syncScrollArea.bind(this)); - this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); + private currentRowHeight: number; + private lastRecordedBufferLength: number; + private lastRecordedViewportHeight: number; - this.syncScrollArea(); + constructor( + private terminal: ITerminal, + private viewportElement: HTMLElement, + private scrollArea: HTMLElement, + private charMeasureElement: HTMLElement + ) { + this.currentRowHeight = 0; + this.lastRecordedBufferLength = 0; + this.lastRecordedViewportHeight = 0; + + this.terminal.on('scroll', this.syncScrollArea.bind(this)); + this.terminal.on('resize', this.syncScrollArea.bind(this)); + this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); + + this.syncScrollArea(); + } + + /** + * Refreshes row height, setting line-height, viewport height and scroll area height if + * necessary. + * @param charSize A character size measurement bounding rect object, if it doesn't exist it will + * be created. + */ + private refresh(charSize?: ClientRect): void { + var size = charSize || this.charMeasureElement.getBoundingClientRect(); + if (size.height > 0) { + var rowHeightChanged = size.height !== this.currentRowHeight; + if (rowHeightChanged) { + this.currentRowHeight = size.height; + this.viewportElement.style.lineHeight = size.height + 'px'; + this.terminal.rowContainer.style.lineHeight = size.height + 'px'; + } + var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows; + if (rowHeightChanged || viewportHeightChanged) { + this.lastRecordedViewportHeight = this.terminal.rows; + this.viewportElement.style.height = size.height * this.terminal.rows + 'px'; + } + this.scrollArea.style.height = (size.height * this.lastRecordedBufferLength) + 'px'; + } + } + + /** + * Updates dimensions and synchronizes the scroll area if necessary. + */ + public syncScrollArea(): void { + if (this.lastRecordedBufferLength !== this.terminal.lines.length) { + // If buffer height changed + this.lastRecordedBufferLength = this.terminal.lines.length; + this.refresh(); + } else if (this.lastRecordedViewportHeight !== this.terminal.rows) { + // If viewport height changed + this.refresh(); + } else { + // If size has changed, refresh viewport + var size = this.charMeasureElement.getBoundingClientRect(); + if (size.height !== this.currentRowHeight) { + this.refresh(size); + } + } + + // Sync scrollTop + var scrollTop = this.terminal.ydisp * this.currentRowHeight; + if (this.viewportElement.scrollTop !== scrollTop) { + this.viewportElement.scrollTop = scrollTop; + } + } + + /** + * Handles scroll events on the viewport, calculating the new viewport and requesting the + * terminal to scroll to it. + * @param ev The scroll event. + */ + private onScroll(ev: Event) { + var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight); + var diff = newRow - this.terminal.ydisp; + this.terminal.scrollDisp(diff, true); + } + + /** + * Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual + * scrolling to `onScroll`, this event needs to be attached manually by the consumer of + * `Viewport`. + * @param ev The mouse wheel event. + */ + public onWheel(ev: WheelEvent) { + if (ev.deltaY === 0) { + // Do nothing if it's not a vertical scroll event + return; + } + // Fallback to WheelEvent.DOM_DELTA_PIXEL + var multiplier = 1; + if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { + multiplier = this.currentRowHeight; + } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + multiplier = this.currentRowHeight * this.terminal.rows; + } + this.viewportElement.scrollTop += ev.deltaY * multiplier; + // Prevent the page from scrolling when the terminal scrolls + ev.preventDefault(); + }; } - -/** - * Refreshes row height, setting line-height, viewport height and scroll area height if - * necessary. - * @param {number|undefined} charSize A character size measurement bounding rect object, if it - * doesn't exist it will be created. - */ -Viewport.prototype.refresh = function(charSize) { - var size = charSize || this.charMeasureElement.getBoundingClientRect(); - if (size.height > 0) { - var rowHeightChanged = size.height !== this.currentRowHeight; - if (rowHeightChanged) { - this.currentRowHeight = size.height; - this.viewportElement.style.lineHeight = size.height + 'px'; - this.terminal.rowContainer.style.lineHeight = size.height + 'px'; - } - var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows; - if (rowHeightChanged || viewportHeightChanged) { - this.lastRecordedViewportHeight = this.terminal.rows; - this.viewportElement.style.height = size.height * this.terminal.rows + 'px'; - } - this.scrollArea.style.height = (size.height * this.lastRecordedBufferLength) + 'px'; - } -}; - -/** - * Updates dimensions and synchronizes the scroll area if necessary. - */ -Viewport.prototype.syncScrollArea = function() { - if (this.lastRecordedBufferLength !== this.terminal.lines.length) { - // If buffer height changed - this.lastRecordedBufferLength = this.terminal.lines.length; - this.refresh(); - } else if (this.lastRecordedViewportHeight !== this.terminal.rows) { - // If viewport height changed - this.refresh(); - } else { - // If size has changed, refresh viewport - var size = this.charMeasureElement.getBoundingClientRect(); - if (size.height !== this.currentRowHeight) { - this.refresh(size); - } - } - - // Sync scrollTop - var scrollTop = this.terminal.ydisp * this.currentRowHeight; - if (this.viewportElement.scrollTop !== scrollTop) { - this.viewportElement.scrollTop = scrollTop; - } -}; - -/** - * Handles scroll events on the viewport, calculating the new viewport and requesting the - * terminal to scroll to it. - * @param {Event} ev The scroll event. - */ -Viewport.prototype.onScroll = function(ev) { - var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight); - var diff = newRow - this.terminal.ydisp; - this.terminal.scrollDisp(diff, true); -}; - -/** - * Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual - * scrolling to `onScroll`, this event needs to be attached manually by the consumer of - * `Viewport`. - * @param {WheelEvent} ev The mouse wheel event. - */ -Viewport.prototype.onWheel = function(ev) { - if (ev.deltaY === 0) { - // Do nothing if it's not a vertical scroll event - return; - } - // Fallback to WheelEvent.DOM_DELTA_PIXEL - var multiplier = 1; - if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { - multiplier = this.currentRowHeight; - } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { - multiplier = this.currentRowHeight * this.terminal.rows; - } - this.viewportElement.scrollTop += ev.deltaY * multiplier; - // Prevent the page from scrolling when the terminal scrolls - ev.preventDefault(); -}; - -export { Viewport }; From 51932161af672cb6c33a00ac786d7045147df889 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 21 Nov 2016 14:39:58 -0800 Subject: [PATCH 5/6] Move viewport-test.js to Viewport.test.ts --- src/{test/viewport-test.js => Viewport.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{test/viewport-test.js => Viewport.test.ts} (100%) diff --git a/src/test/viewport-test.js b/src/Viewport.test.ts similarity index 100% rename from src/test/viewport-test.js rename to src/Viewport.test.ts From 5bddb9374586b50ccf4325d0d361f6522cdc9a9e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 21 Nov 2016 14:44:10 -0800 Subject: [PATCH 6/6] Adopt more TS syntax in Viewport.test.ts --- src/Viewport.test.ts | 38 ++++++++++++++++---------------------- src/Viewport.ts | 1 - 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/Viewport.test.ts b/src/Viewport.test.ts index 376fcdc8..5b106b43 100644 --- a/src/Viewport.test.ts +++ b/src/Viewport.test.ts @@ -1,21 +1,21 @@ -var assert = require('chai').assert; -var Terminal = require('../xterm'); +import { assert } from 'chai'; +import { Viewport } from './Viewport'; -describe('Viewport', function () { +describe('Viewport', () => { var terminal; var viewportElement; var charMeasureElement; var viewport; var scrollAreaElement; - var CHARACTER_HEIGHT = 10; + const CHARACTER_HEIGHT = 10; - beforeEach(function () { + beforeEach(() => { terminal = { lines: [], rows: 0, ydisp: 0, - on: function () {}, + on: () => {}, rowContainer: { style: { lineHeight: 0 @@ -23,7 +23,7 @@ describe('Viewport', function () { } }; viewportElement = { - addEventListener: function () {}, + addEventListener: () => {}, style: { height: 0, lineHeight: 0 @@ -35,37 +35,31 @@ describe('Viewport', function () { } }; charMeasureElement = { - getBoundingClientRect: function () { + getBoundingClientRect: () => { return { width: null, height: CHARACTER_HEIGHT }; } }; - viewport = new Terminal.Viewport(terminal, viewportElement, scrollAreaElement, charMeasureElement); + viewport = new Viewport(terminal, viewportElement, scrollAreaElement, charMeasureElement); }); - describe('Public API', function () { - it('should define Viewport.prototype.onWheel', function () { - assert.isDefined(Terminal.Viewport.prototype.onWheel); - }); - }); - - describe('refresh', function () { - it('should set the line-height of the terminal', function () { + describe('refresh', () => { + it('should set the line-height of the terminal', () => { assert.equal(viewportElement.style.lineHeight, CHARACTER_HEIGHT + 'px'); assert.equal(terminal.rowContainer.style.lineHeight, CHARACTER_HEIGHT + 'px'); - charMeasureElement.getBoundingClientRect = function () { + charMeasureElement.getBoundingClientRect = () => { return { width: null, height: 1 }; }; viewport.refresh(); assert.equal(viewportElement.style.lineHeight, '1px'); assert.equal(terminal.rowContainer.style.lineHeight, '1px'); }); - it('should set the height of the viewport when the line-height changed', function () { + it('should set the height of the viewport when the line-height changed', () => { terminal.lines.push(''); terminal.lines.push(''); terminal.rows = 1; viewport.refresh(); assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px'); - charMeasureElement.getBoundingClientRect = function () { + charMeasureElement.getBoundingClientRect = () => { return { width: null, height: 20 }; }; viewport.refresh(); @@ -73,8 +67,8 @@ describe('Viewport', function () { }); }); - describe('syncScrollArea', function () { - it('should sync the scroll area', function () { + describe('syncScrollArea', () => { + it('should sync the scroll area', () => { terminal.lines.push(''); terminal.rows = 1; assert.equal(scrollAreaElement.style.height, 0 * CHARACTER_HEIGHT + 'px'); diff --git a/src/Viewport.ts b/src/Viewport.ts index 573a050c..11889dae 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -13,7 +13,6 @@ import { ITerminal } from './Interfaces'; * @param charMeasureElement A DOM element used to measure the character size of. the terminal. */ export class Viewport { - private currentRowHeight: number; private lastRecordedBufferLength: number; private lastRecordedViewportHeight: number;