From 290394b401c53e096f4659daea4eb1c3417e9e9d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 3 Jan 2019 20:22:53 -0800 Subject: [PATCH 01/97] New addon prototype Part of #1128 --- demo/client.ts | 5 +- src/Terminal.test.ts | 2 +- src/Terminal.ts | 17 ++++- src/addons/webLinks/webLinks.ts | 20 +++++- src/public/Terminal.ts | 11 +++- src/ui/AddonManager.test.ts | 111 ++++++++++++++++++++++++++++++++ src/ui/AddonManager.ts | 68 +++++++++++++++++++ src/ui/TestUtils.test.ts | 11 +++- typings/xterm.d.ts | 23 +++++++ 9 files changed, 261 insertions(+), 7 deletions(-) create mode 100644 src/ui/AddonManager.test.ts create mode 100644 src/ui/AddonManager.ts diff --git a/demo/client.ts b/demo/client.ts index a3a912f6..ae628baa 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -29,7 +29,7 @@ Terminal.applyAddon(attach); Terminal.applyAddon(fit); Terminal.applyAddon(fullscreen); Terminal.applyAddon(search); -Terminal.applyAddon(webLinks); +// Terminal.applyAddon(webLinks); Terminal.applyAddon(winptyCompat); @@ -84,6 +84,7 @@ function createTerminal(): void { terminalContainer.removeChild(terminalContainer.children[0]); } term = new Terminal({}); + (term as TerminalType).loadAddon(webLinks.WebLinksAddon).init(); window.term = term; // Expose `term` to window for debugging purposes term.on('resize', (size: { cols: number, rows: number }) => { if (!pid) { @@ -100,7 +101,7 @@ function createTerminal(): void { term.open(terminalContainer); term.winptyCompatInit(); - term.webLinksInit(); + // term.webLinksInit(); term.fit(); term.focus(); diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index fdc9678b..4807ddde 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -16,7 +16,7 @@ class TestTerminal extends Terminal { public keyPress(ev: any): boolean { return this._keyPress(ev); } } -describe('term.js addons', () => { +describe('Terminal', () => { let term: TestTerminal; const termOptions = { cols: INIT_COLS, diff --git a/src/Terminal.ts b/src/Terminal.ts index bc97de29..9e0ef6b9 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -45,13 +45,14 @@ import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; import { MouseZoneManager } from './ui/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './ui/ScreenDprMonitor'; -import { ITheme, IMarker, IDisposable } from 'xterm'; +import { ITheme, IMarker, IDisposable, ITerminalAddon, ITerminalAddonConstructor } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; import { KeyboardResultType, ICharset } from './core/Types'; import { clone } from './common/Clone'; +import { AddonManager } from './ui/AddonManager'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -203,6 +204,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _mouseZoneManager: IMouseZoneManager; public mouseHelper: MouseHelper; private _accessibilityManager: AccessibilityManager; + private _addonManager: AddonManager; private _screenDprMonitor: ScreenDprMonitor; private _theme: ITheme; @@ -309,6 +311,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.linkifier = this.linkifier || new Linkifier(this); this._mouseZoneManager = this._mouseZoneManager || null; this.soundManager = this.soundManager || new SoundManager(this); + this._addonManager = this._addonManager || new AddonManager(); // Create the terminal's buffers and set the current buffer this.buffers = new BufferSet(this); @@ -1933,6 +1936,18 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // return this.options.bellStyle === 'sound' || // this.options.bellStyle === 'both'; } + + public loadAddon(addonConstructor: ITerminalAddonConstructor): T { + return this._addonManager.loadAddon(this, addonConstructor); + } + + public disposeAddon(addonConstructor: ITerminalAddonConstructor): void { + this._addonManager.disposeAddon(addonConstructor); + } + + public getAddon(addonConstructor: ITerminalAddonConstructor): T { + return this._addonManager.getAddon(addonConstructor); + } } /** diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts index f0d69cc5..6fc6b25d 100644 --- a/src/addons/webLinks/webLinks.ts +++ b/src/addons/webLinks/webLinks.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal, ILinkMatcherOptions } from 'xterm'; +import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm'; const protocolClause = '(https?:\\/\\/)'; const domainCharacterSet = '[\\da-z\\.-]+'; @@ -35,12 +35,30 @@ function handleLink(event: MouseEvent, uri: string): void { * @param options Custom options to use, matchIndex will always be ignored. */ export function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void { + // TODO: Remove this options.matchIndex = 1; term.registerLinkMatcher(strictUrlRegex, handler, options); } export function apply(terminalConstructor: typeof Terminal): void { + // TODO: Remove this (terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void { webLinksInit(this, handler, options); }; } + +export class WebLinksAddon implements ITerminalAddon { + private _linkMatcherId: number; + + constructor(private _terminal: Terminal) { + } + + public init(handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void { + options.matchIndex = 1; + this._linkMatcherId = this._terminal.registerLinkMatcher(strictUrlRegex, handler, options); + } + + public dispose(): void { + this._terminal.deregisterLinkMatcher(this._linkMatcherId); + } +} diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 87fcfaef..6f2de10f 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings } from 'xterm'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ITerminalAddonConstructor } from 'xterm'; import { ITerminal } from '../Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; @@ -154,6 +154,15 @@ export class Terminal implements ITerminalApi { public static applyAddon(addon: any): void { addon.apply(Terminal); } + public loadAddon(addonConstructor: ITerminalAddonConstructor): T { + return this._core.loadAddon(addonConstructor); + } + public getAddon(addonConstructor: ITerminalAddonConstructor): T { + return this._core.getAddon(addonConstructor); + } + public disposeAddon(addonConstructor: ITerminalAddonConstructor): void { + this._core.disposeAddon(addonConstructor); + } public static get strings(): ILocalizableStrings { return Strings; } diff --git a/src/ui/AddonManager.test.ts b/src/ui/AddonManager.test.ts new file mode 100644 index 00000000..ac83917c --- /dev/null +++ b/src/ui/AddonManager.test.ts @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { AddonManager, ILoadedAddon } from './AddonManager'; +import { ITerminalAddon } from 'xterm'; + +class TestAddonManager extends AddonManager { + public get addons(): ILoadedAddon[] { + return this._addons; + } +} + +describe('AddonManager', () => { + let manager: TestAddonManager; + + beforeEach(() => { + manager = new TestAddonManager(); + }); + + describe('loadAddon', () => { + it('should call addon constructor', () => { + let called = false; + class Addon implements ITerminalAddon { + constructor(terminal: any) { + assert.equal(terminal, 'foo', 'The first constructor arg should be Terminal'); + called = true; + } + dispose(): void { } + } + manager.loadAddon('foo' as any, Addon); + assert.equal(called, true); + }); + }); + + describe('getAddon', () => { + it('should fetch registered addons', () => { + class BaseAddon implements ITerminalAddon { + constructor() { } + dispose(): void { } + } + class Addon1 extends BaseAddon { } + class Addon2 extends BaseAddon { } + class Addon3 extends BaseAddon { } + const addon1 = manager.loadAddon(null, Addon1); + assert.equal(manager.getAddon(Addon1), addon1); + assert.equal(manager.addons.length, 1); + const addon2 = manager.loadAddon(null, Addon2); + assert.equal(manager.getAddon(Addon1), addon1); + assert.equal(manager.getAddon(Addon2), addon2); + assert.equal(manager.addons.length, 2); + const addon3 = manager.loadAddon(null, Addon3); + assert.equal(manager.getAddon(Addon1), addon1); + assert.equal(manager.getAddon(Addon2), addon2); + assert.equal(manager.getAddon(Addon3), addon3); + assert.equal(manager.addons.length, 3); + }); + }); + + describe('disposeAddon', () => { + it('should dispose the loaded addon and remove it from the loaded list', () => { + let called = 0; + class BaseAddon implements ITerminalAddon { + constructor() { } + dispose(): void { + called++; + } + } + class Addon1 extends BaseAddon { } + class Addon2 extends BaseAddon { } + class Addon3 extends BaseAddon { } + manager.loadAddon(null, Addon1); + manager.loadAddon(null, Addon2); + manager.loadAddon(null, Addon3); + assert.equal(manager.addons.length, 3); + manager.disposeAddon(Addon1); + assert.equal(called, 1); + assert.equal(manager.addons.length, 2); + manager.disposeAddon(Addon2); + assert.equal(called, 2); + assert.equal(manager.addons.length, 1); + manager.disposeAddon(Addon3); + assert.equal(called, 3); + assert.equal(manager.addons.length, 0); + }); + }); + + describe('dispose', () => { + it('should dispose all loaded addons', () => { + let called = 0; + class BaseAddon implements ITerminalAddon { + constructor() { } + dispose(): void { + called++; + } + } + class Addon1 extends BaseAddon { } + class Addon2 extends BaseAddon { } + class Addon3 extends BaseAddon { } + manager.loadAddon(null, Addon1); + manager.loadAddon(null, Addon2); + manager.loadAddon(null, Addon3); + assert.equal(manager.addons.length, 3); + manager.dispose(); + assert.equal(called, 3); + assert.equal(manager.addons.length, 0); + }); + }); +}); diff --git a/src/ui/AddonManager.ts b/src/ui/AddonManager.ts new file mode 100644 index 00000000..b55bd6a8 --- /dev/null +++ b/src/ui/AddonManager.ts @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ITerminalAddon, ITerminalAddonConstructor, IDisposable, Terminal } from 'xterm'; + +export interface ILoadedAddon { + ctor: ITerminalAddonConstructor; + instance: ITerminalAddon; + dispose: () => void; +} + +export class AddonManager implements IDisposable { + protected _addons: ILoadedAddon[] = []; + + constructor() { + } + + public dispose(): void { + for (let i = this._addons.length - 1; i >= 0; i--) { + this._addons[i].instance.dispose(); + } + } + + public loadAddon(terminal: Terminal, addonConstructor: ITerminalAddonConstructor): T { + const instance = new addonConstructor(terminal); + const loadedAddon: ILoadedAddon = { + ctor: addonConstructor, + instance, + dispose: instance.dispose + }; + this._addons.push(loadedAddon); + instance.dispose = () => this._wrappedAddonDispose(loadedAddon); + return instance; + } + + public disposeAddon(addonConstructor: ITerminalAddonConstructor): void { + const match = this._addons.find(value => value.ctor === addonConstructor); + if (!match) { + throw new Error('Could not dispose an addon that has not been loaded'); + } + match.instance.dispose(); + } + + public getAddon(addonConstructor: ITerminalAddonConstructor): T { + const match = this._addons.find(value => value.ctor === addonConstructor); + if (!match) { + return undefined; + } + return match.instance as T; + } + + private _wrappedAddonDispose(loadedAddon: ILoadedAddon): void { + let index = -1; + for (let i = 0; i < this._addons.length; i++) { + if (this._addons[i].ctor === loadedAddon.ctor) { + index = i; + break; + } + } + if (index === -1) { + throw new Error('Could not dispose an addon that has not been loaded'); + } + loadedAddon.dispose(); + this._addons.splice(index, 1); + } +} diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index e6e4aaa3..fdcdd4e5 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -8,7 +8,7 @@ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuff import { ICircularList, XtermListener } from '../common/Types'; import { Buffer } from '../Buffer'; import * as Browser from '../core/Platform'; -import { ITheme, IDisposable, IMarker } from 'xterm'; +import { ITheme, IDisposable, IMarker, ITerminalAddon, ITerminalAddonConstructor } from 'xterm'; import { Terminal } from '../Terminal'; export class TestTerminal extends Terminal { @@ -19,6 +19,15 @@ export class TestTerminal extends Terminal { } export class MockTerminal implements ITerminal { + loadAddon(addonConstructor: ITerminalAddonConstructor): T { + throw new Error('Method not implemented.'); + } + disposeAddon(addonConstructor: ITerminalAddonConstructor): void { + throw new Error('Method not implemented.'); + } + getAddon(addonConstructor: ITerminalAddonConstructor): T { + throw new Error('Method not implemented.'); + } markers: IMarker[]; addMarker(cursorYOffset: number): IMarker { throw new Error('Method not implemented.'); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index c5d2b020..d73a9156 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -753,5 +753,28 @@ declare module 'xterm' { * @param addon The addon to apply. */ static applyAddon(addon: any): void; + + loadAddon(addonConstructor: ITerminalAddonConstructor): T; + disposeAddon(addonConstructor: ITerminalAddonConstructor): void; + getAddon(addonConstructor: ITerminalAddonConstructor): T; + } + + export interface ITerminalAddonConstructor { + new(terminal: Terminal): T; + } + + export interface ITerminalAddon { + /** + * This property declares all addon dependencies that must be intialized + * before this addon can be constructed. For addons with no dependencies + * just don't include this property. + */ + // readonly DEPENDENCIES?: ITerminalAddonConstructor[]; + + /** + * This function includes anything that needs to happen to clean up when + * the addon is being disposed. + */ + dispose(): void; } } From 7ba17ca7703b8ef9f682429c8fadee26d0f8df8e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 3 Jan 2019 22:19:30 -0800 Subject: [PATCH 02/97] Convert web links and attach to modules --- demo/client.ts | 17 +-- demo/start.js | 3 +- package.json | 6 +- src/addons/attach/Interfaces.ts | 22 ---- src/addons/attach/attach.test.ts | 20 ---- src/addons/attach/attach.ts | 155 --------------------------- src/addons/attach/index.html | 93 ---------------- src/addons/attach/package.json | 5 - src/addons/attach/tsconfig.json | 20 ---- src/addons/webLinks/package.json | 5 - src/addons/webLinks/tsconfig.json | 23 ---- src/addons/webLinks/webLinks.test.ts | 90 ---------------- src/addons/webLinks/webLinks.ts | 64 ----------- yarn.lock | 10 ++ 14 files changed, 27 insertions(+), 506 deletions(-) delete mode 100644 src/addons/attach/Interfaces.ts delete mode 100644 src/addons/attach/attach.test.ts delete mode 100644 src/addons/attach/attach.ts delete mode 100644 src/addons/attach/index.html delete mode 100644 src/addons/attach/package.json delete mode 100644 src/addons/attach/tsconfig.json delete mode 100644 src/addons/webLinks/package.json delete mode 100644 src/addons/webLinks/tsconfig.json delete mode 100644 src/addons/webLinks/webLinks.test.ts delete mode 100644 src/addons/webLinks/webLinks.ts diff --git a/demo/client.ts b/demo/client.ts index ae628baa..40679477 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -8,11 +8,12 @@ /// import { Terminal } from '../lib/public/Terminal'; -import * as attach from '../lib/addons/attach/attach'; +import { AttachAddon } from 'xterm-addon-attach'; +import { WebLinksAddon } from 'xterm-addon-web-links'; + import * as fit from '../lib/addons/fit/fit'; import * as fullscreen from '../lib/addons/fullscreen/fullscreen'; import * as search from '../lib/addons/search/search'; -import * as webLinks from '../lib/addons/webLinks/webLinks'; import * as winptyCompat from '../lib/addons/winptyCompat/winptyCompat'; import { ISearchOptions } from '../lib/addons/search/Interfaces'; @@ -25,15 +26,14 @@ export interface IWindowWithTerminal extends Window { } declare let window: IWindowWithTerminal; -Terminal.applyAddon(attach); Terminal.applyAddon(fit); Terminal.applyAddon(fullscreen); Terminal.applyAddon(search); -// Terminal.applyAddon(webLinks); Terminal.applyAddon(winptyCompat); let term; +let attachAddon: AttachAddon; let protocol; let socketURL; let socket; @@ -84,7 +84,12 @@ function createTerminal(): void { terminalContainer.removeChild(terminalContainer.children[0]); } term = new Terminal({}); - (term as TerminalType).loadAddon(webLinks.WebLinksAddon).init(); + + // Load addons + const typedTerm = term as TerminalType; + typedTerm.loadAddon(WebLinksAddon).init(); + attachAddon = typedTerm.loadAddon(AttachAddon); + window.term = term; // Expose `term` to window for debugging purposes term.on('resize', (size: { cols: number, rows: number }) => { if (!pid) { @@ -144,7 +149,7 @@ function createTerminal(): void { } function runRealTerminal(): void { - term.attach(socket); + attachAddon.attach(socket); term._initialized = true; } diff --git a/demo/start.js b/demo/start.js index 78f1ff1d..1796dc17 100644 --- a/demo/start.js +++ b/demo/start.js @@ -26,7 +26,8 @@ const clientConfig = { { test: /\.js$/, use: ["source-map-loader"], - enforce: "pre" + enforce: "pre", + exclude: /node_modules/ } ] }, diff --git a/package.json b/package.json index 5ed3a4b8..ab101fca 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,8 @@ "vinyl-source-stream": "^1.1.0", "webpack": "^4.17.1", "webpack-cli": "^3.1.0", + "xterm-addon-attach": "0.1.0-beta4", + "xterm-addon-web-links": "0.1.0-beta3", "zmodem.js": "^0.1.5" }, "scripts": { @@ -58,12 +60,12 @@ "test-coverage": "nyc -x gulpfile.js -x '**/*test*' npm run mocha", "mocha": "gulp test", "tsc": "tsc", - "prebuild": "concurrently --kill-others-on-fail --names \"lib,attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem,css\" \"tsc\" \"tsc -p ./src/addons/attach\" \"tsc -p ./src/addons/fit\" \"tsc -p ./src/addons/fullscreen\" \"tsc -p ./src/addons/search\" \"tsc -p ./src/addons/terminado\" \"tsc -p ./src/addons/webLinks\" \"tsc -p ./src/addons/winptyCompat\" \"tsc -p ./src/addons/zmodem\" \"gulp css\"", + "prebuild": "concurrently --kill-others-on-fail --names \"lib,fit,fullscreen,search,terminado,winptyCompat,zmodem,css\" \"tsc\" \"tsc -p ./src/addons/fit\" \"tsc -p ./src/addons/fullscreen\" \"tsc -p ./src/addons/search\" \"tsc -p ./src/addons/terminado\" \"tsc -p ./src/addons/winptyCompat\" \"tsc -p ./src/addons/zmodem\" \"gulp css\"", "build": "gulp build", "prepublish": "npm run build", "coveralls": "nyc report --reporter=text-lcov | coveralls", "watch": "concurrently --kill-others-on-fail --names \"lib,css\" \"tsc -w\" \"gulp watch-css\"", - "watch-addons": "concurrently --kill-others-on-fail --names \"attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem\" \"tsc -w -p ./src/addons/attach\" \"tsc -w -p ./src/addons/fit\" \"tsc -w -p ./src/addons/fullscreen\" \"tsc -w -p ./src/addons/search\" \"tsc -w -p ./src/addons/terminado\" \"tsc -w -p ./src/addons/webLinks\" \"tsc -w -p ./src/addons/winptyCompat\" \"tsc -w -p ./src/addons/zmodem\"", + "watch-addons": "concurrently --kill-others-on-fail --names \"fit,fullscreen,search,terminado,winptyCompat,zmodem\" \"tsc -w -p ./src/addons/fit\" \"tsc -w -p ./src/addons/fullscreen\" \"tsc -w -p ./src/addons/search\" \"tsc -w -p ./src/addons/terminado\" \"tsc -w -p ./src/addons/winptyCompat\" \"tsc -w -p ./src/addons/zmodem\"", "layering": "concurrently --kill-others-on-fail --names \"common,core\" \"tsc -p ./src/common\" \"tsc -p ./src/core\"" } } diff --git a/src/addons/attach/Interfaces.ts b/src/addons/attach/Interfaces.ts deleted file mode 100644 index ab5846f5..00000000 --- a/src/addons/attach/Interfaces.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - * - * Implements the attach method, that attaches the terminal to a WebSocket stream. - */ - -import { Terminal, IDisposable } from 'xterm'; - -export interface IAttachAddonTerminal extends Terminal { - _core: { - register(d: T): void; - }; - - __socket?: WebSocket; - __attachSocketBuffer?: string; - - __getMessage?(ev: MessageEvent): void; - __flushBuffer?(): void; - __pushToBuffer?(data: string): void; - __sendData?(data: string): void; -} diff --git a/src/addons/attach/attach.test.ts b/src/addons/attach/attach.test.ts deleted file mode 100644 index e280b656..00000000 --- a/src/addons/attach/attach.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2014 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; - -import * as attach from './attach'; - -class MockTerminal {} - -describe('attach addon', () => { - describe('apply', () => { - it('should do register the `attach` and `detach` methods', () => { - attach.apply(MockTerminal); - assert.equal(typeof (MockTerminal).prototype.attach, 'function'); - assert.equal(typeof (MockTerminal).prototype.detach, 'function'); - }); - }); -}); diff --git a/src/addons/attach/attach.ts b/src/addons/attach/attach.ts deleted file mode 100644 index f121e2e2..00000000 --- a/src/addons/attach/attach.ts +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright (c) 2014 The xterm.js authors. All rights reserved. - * @license MIT - * - * Implements the attach method, that attaches the terminal to a WebSocket stream. - */ - -import { Terminal, IDisposable } from 'xterm'; -import { IAttachAddonTerminal } from './Interfaces'; - -/** - * Attaches the given terminal to the given socket. - * - * @param term The terminal to be attached to the given socket. - * @param socket The socket to attach the current terminal. - * @param bidirectional Whether the terminal should send data to the socket as well. - * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum - * frequency of 1 rendering per 10ms. - */ -export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void { - const addonTerminal = term; - bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional; - addonTerminal.__socket = socket; - - addonTerminal.__flushBuffer = () => { - addonTerminal.write(addonTerminal.__attachSocketBuffer); - addonTerminal.__attachSocketBuffer = null; - }; - - addonTerminal.__pushToBuffer = (data: string) => { - if (addonTerminal.__attachSocketBuffer) { - addonTerminal.__attachSocketBuffer += data; - } else { - addonTerminal.__attachSocketBuffer = data; - setTimeout(addonTerminal.__flushBuffer, 10); - } - }; - - // TODO: This should be typed but there seem to be issues importing the type - let myTextDecoder: any; - - addonTerminal.__getMessage = function(ev: MessageEvent): void { - let str: string; - - if (typeof ev.data === 'object') { - if (!myTextDecoder) { - myTextDecoder = new TextDecoder(); - } - if (ev.data instanceof ArrayBuffer) { - str = myTextDecoder.decode(ev.data); - displayData(str); - } else { - const fileReader = new FileReader(); - - fileReader.addEventListener('load', () => { - str = myTextDecoder.decode(fileReader.result); - displayData(str); - }); - fileReader.readAsArrayBuffer(ev.data); - } - } else if (typeof ev.data === 'string') { - displayData(ev.data); - } else { - throw Error(`Cannot handle "${typeof ev.data}" websocket message.`); - } - }; - - /** - * Push data to buffer or write it in the terminal. - * This is used as a callback for FileReader.onload. - * - * @param str String decoded by FileReader. - * @param data The data of the EventMessage. - */ - function displayData(str?: string, data?: string): void { - if (buffered) { - addonTerminal.__pushToBuffer(str || data); - } else { - addonTerminal.write(str || data); - } - } - - addonTerminal.__sendData = (data: string) => { - if (socket.readyState !== 1) { - return; - } - socket.send(data); - }; - - addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage)); - - if (bidirectional) { - addonTerminal._core.register(addonTerminal.addDisposableListener('data', addonTerminal.__sendData)); - } - - addonTerminal._core.register(addSocketListener(socket, 'close', () => detach(addonTerminal, socket))); - addonTerminal._core.register(addSocketListener(socket, 'error', () => detach(addonTerminal, socket))); -} - -function addSocketListener(socket: WebSocket, type: string, handler: (this: WebSocket, ev: Event) => any): IDisposable { - socket.addEventListener(type, handler); - return { - dispose: () => { - if (!handler) { - // Already disposed - return; - } - socket.removeEventListener(type, handler); - handler = null; - } - }; -} - -/** - * Detaches the given terminal from the given socket - * - * @param term The terminal to be detached from the given socket. - * @param socket The socket from which to detach the current terminal. - */ -export function detach(term: Terminal, socket: WebSocket): void { - const addonTerminal = term; - addonTerminal.off('data', addonTerminal.__sendData); - - socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket; - - if (socket) { - socket.removeEventListener('message', addonTerminal.__getMessage); - } - - delete addonTerminal.__socket; -} - - -export function apply(terminalConstructor: typeof Terminal): void { - /** - * Attaches the current terminal to the given socket - * - * @param socket The socket to attach the current terminal. - * @param bidirectional Whether the terminal should send data to the socket as well. - * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum - * frequency of 1 rendering per 10ms. - */ - (terminalConstructor.prototype).attach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void { - attach(this, socket, bidirectional, buffered); - }; - - /** - * Detaches the current terminal from the given socket. - * - * @param socket The socket from which to detach the current terminal. - */ - (terminalConstructor.prototype).detach = function (socket: WebSocket): void { - detach(this, socket); - }; -} diff --git a/src/addons/attach/index.html b/src/addons/attach/index.html deleted file mode 100644 index b6f853be..00000000 --- a/src/addons/attach/index.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - -
- -

- xterm.js: socket attach -

-

- Attach the terminal to a WebSocket terminal stream with ease. Perfect for attaching to your - Docker containers. -

-

- Socket information -

-
- - -
-
- -
- - - \ No newline at end of file diff --git a/src/addons/attach/package.json b/src/addons/attach/package.json deleted file mode 100644 index 9e45068b..00000000 --- a/src/addons/attach/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "xterm.attach", - "main": "attach.js", - "private": true -} diff --git a/src/addons/attach/tsconfig.json b/src/addons/attach/tsconfig.json deleted file mode 100644 index 359fbd24..00000000 --- a/src/addons/attach/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es5", - "lib": [ - "dom", - "es6", - ], - "rootDir": ".", - "outDir": "../../../lib/addons/attach/", - "sourceMap": true, - "removeComments": true, - "declaration": true, - "preserveWatchOutput": true - }, - "include": [ - "**/*.ts", - "../../../typings/xterm.d.ts" - ] -} diff --git a/src/addons/webLinks/package.json b/src/addons/webLinks/package.json deleted file mode 100644 index f200cab4..00000000 --- a/src/addons/webLinks/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "xterm.weblinks", - "main": "weblinks.js", - "private": true -} diff --git a/src/addons/webLinks/tsconfig.json b/src/addons/webLinks/tsconfig.json deleted file mode 100644 index 18105aa2..00000000 --- a/src/addons/webLinks/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es5", - "lib": [ - "dom", - "es5", - ], - "rootDir": ".", - "outDir": "../../../lib/addons/webLinks/", - "sourceMap": true, - "removeComments": true, - "declaration": true, - "preserveWatchOutput": true, - "types": [ - "../../node_modules/@types/mocha" - ] - }, - "include": [ - "**/*.ts", - "../../../typings/xterm.d.ts" - ] -} diff --git a/src/addons/webLinks/webLinks.test.ts b/src/addons/webLinks/webLinks.test.ts deleted file mode 100644 index 1e8a4ae7..00000000 --- a/src/addons/webLinks/webLinks.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; - -import * as webLinks from './webLinks'; - -class MockTerminal { - public regex: RegExp; - public handler: (event: MouseEvent, uri: string) => void; - public options?: any; - - public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: any): number { - this.regex = regex; - this.handler = handler; - this.options = options; - return 0; - } -} - -describe('webLinks addon', () => { - describe('apply', () => { - it('should do register the `webLinksInit` method', () => { - webLinks.apply(MockTerminal); - assert.equal(typeof (MockTerminal).prototype.webLinksInit, 'function'); - }); - }); - - it('should allow ~ character in URI path', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); - - const row = ' http://foo.com/a~b#c~d?e~f '; - - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; - - assert.equal(uri, 'http://foo.com/a~b#c~d?e~f'); - }); - - it('should allow : character in URI path', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); - - const row = ' http://foo.com/colon:test '; - - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; - - assert.equal(uri, 'http://foo.com/colon:test'); - }); - - it('should not allow : character at the end of a URI path', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); - - const row = ' http://foo.com/colon:test: '; - - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; - - assert.equal(uri, 'http://foo.com/colon:test'); - }); - - it('should not allow " character at the end of a URI enclosed with ""', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); - - const row = '"http://foo.com/"'; - - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; - - assert.equal(uri, 'http://foo.com/'); - }); - - it('should not allow \' character at the end of a URI enclosed with \'\'', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); - - const row = '\'http://foo.com/\''; - - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; - - assert.equal(uri, 'http://foo.com/'); - }); -}); diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts deleted file mode 100644 index 6fc6b25d..00000000 --- a/src/addons/webLinks/webLinks.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm'; - -const protocolClause = '(https?:\\/\\/)'; -const domainCharacterSet = '[\\da-z\\.-]+'; -const negatedDomainCharacterSet = '[^\\da-z\\.-]+'; -const domainBodyClause = '(' + domainCharacterSet + ')'; -const tldClause = '([a-z\\.]{2,6})'; -const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})'; -const localHostClause = '(localhost)'; -const portClause = '(:\\d{1,5})'; -const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?'; -const pathClause = '(\\/[\\/\\w\\.\\-%~:]*)*([^:"\'\\s])'; -const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; -const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; -const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?'; -const negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+'; -const bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause; -const start = '(?:^|' + negatedDomainCharacterSet + ')('; -const end = ')($|' + negatedPathCharacterSet + ')'; -const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end); - -function handleLink(event: MouseEvent, uri: string): void { - window.open(uri, '_blank'); -} - -/** - * Initialize the web links addon, registering the link matcher. - * @param term The terminal to use web links within. - * @param handler A custom handler to use. - * @param options Custom options to use, matchIndex will always be ignored. - */ -export function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void { - // TODO: Remove this - options.matchIndex = 1; - term.registerLinkMatcher(strictUrlRegex, handler, options); -} - -export function apply(terminalConstructor: typeof Terminal): void { - // TODO: Remove this - (terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void { - webLinksInit(this, handler, options); - }; -} - -export class WebLinksAddon implements ITerminalAddon { - private _linkMatcherId: number; - - constructor(private _terminal: Terminal) { - } - - public init(handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void { - options.matchIndex = 1; - this._linkMatcherId = this._terminal.registerLinkMatcher(strictUrlRegex, handler, options); - } - - public dispose(): void { - this._terminal.deregisterLinkMatcher(this._linkMatcherId); - } -} diff --git a/yarn.lock b/yarn.lock index 5555321d..e345f6d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7201,6 +7201,16 @@ xregexp@4.0.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= +xterm-addon-attach@0.1.0-beta4: + version "0.1.0-beta4" + resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.1.0-beta4.tgz#546010f66533f22bfad7605345e44ed95d90a1de" + integrity sha512-HwxNoNS1Fxoo6+MPZJ+5+sMTQHrZEcptL4qstHlaERqxL7ei/lvKMpSRfIo1eRNqxL4vHzYkNWJO7QLATmdalA== + +xterm-addon-web-links@0.1.0-beta3: + version "0.1.0-beta3" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta3.tgz#bd2d45d399340bd1b5bbf44850a0be9c91a1451e" + integrity sha512-nkgwAYZXS97zL650MTl6RnA/iXYAD0yOVf/28+ZTlLQZJVYH2DP22rhOK0aqO+tWX91WUrvkcqxFCY965fomaQ== + y18n@^3.2.0, y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" From 6bdd38089015c5a1e76e04059d99893bff06bbf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 20 Jan 2019 21:54:58 +0100 Subject: [PATCH 03/97] add utf8 decoder --- package.json | 2 + src/core/input/TextDecoder.test.ts | 136 +++++++++++++++- src/core/input/TextDecoder.ts | 245 ++++++++++++++++++++++++++++- 3 files changed, 375 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 5ed3a4b8..b223c55f 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@types/jsdom": "11.0.1", "@types/mocha": "^2.2.33", "@types/node": "6.0.108", + "@types/utf8": "^2.1.6", "@types/webpack": "^4.4.11", "browserify": "^13.3.0", "chai": "3.5.0", @@ -39,6 +40,7 @@ "tslint": "^5.9.1", "tslint-consistent-codestyle": "^1.13.0", "typescript": "3.1", + "utf8": "^3.0.0", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", "webpack": "^4.17.1", diff --git a/src/core/input/TextDecoder.test.ts b/src/core/input/TextDecoder.test.ts index f69fbded..f38a5569 100644 --- a/src/core/input/TextDecoder.test.ts +++ b/src/core/input/TextDecoder.test.ts @@ -4,7 +4,8 @@ */ import { assert } from 'chai'; -import { StringToUtf32, stringFromCodePoint } from './TextDecoder'; +import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from './TextDecoder'; +import { encode } from 'utf8'; // convert UTF32 codepoints to string @@ -19,7 +20,30 @@ function toString(data: Uint32Array, length: number): string { return result; } -describe('StringToUtf32 Decoder', () => { +// convert "bytestring" (charCode 0-255) to bytes +function fromByteString(s: string): Uint8Array { + const result = new Uint8Array(s.length); + for (let i = 0; i < s.length; ++i) { + result[i] = s.charCodeAt(i); + } + return result; +} + + +const TEST_STRINGS = [ + 'Лорем ипсум долор сит амет, ех сеа аццусам диссентиет. Ан еос стет еирмод витуперата. Иус дицерет урбанитас ет. Ан при алтера долорес сплендиде, цу яуо интегре денияуе, игнота волуптариа инструцтиор цу вим.', + 'ლორემ იფსუმ დოლორ სით ამეთ, ფაცერ მუციუს ცონსეთეთურ ყუო იდ, ფერ ვივენდუმ ყუაერენდუმ ეა, ესთ ამეთ მოვეთ სუავითათე ცუ. ვითაე სენსიბუს ან ვიხ. ეხერცი დეთერრუისსეთ უთ ყუი. ვოცენთ დებითის ადიფისცი ეთ ფერ. ნეც ან ფეუგაით ფორენსიბუს ინთერესსეთ. იდ დიცო რიდენს იუს. დისსენთიეთ ცონსეყუუნთურ სედ ნე, ნოვუმ მუნერე ეუმ ათ, ნე ეუმ ნიჰილ ირაცუნდია ურბანითას.', + 'अधिकांश अमितकुमार प्रोत्साहित मुख्य जाने प्रसारन विश्लेषण विश्व दारी अनुवादक अधिकांश नवंबर विषय गटकउसि गोपनीयता विकास जनित परस्पर गटकउसि अन्तरराष्ट्रीयकरन होसके मानव पुर्णता कम्प्युटर यन्त्रालय प्रति साधन', + '覧六子当聞社計文護行情投身斗来。増落世的況上席備界先関権能万。本物挙歯乳全事携供板栃果以。頭月患端撤競見界記引去法条公泊候。決海備駆取品目芸方用朝示上用報。講申務紙約週堂出応理田流団幸稿。起保帯吉対阜庭支肯豪彰属本躍。量抑熊事府募動極都掲仮読岸。自続工就断庫指北速配鳴約事新住米信中験。婚浜袋著金市生交保他取情距。', + '八メル務問へふらく博辞説いわょ読全タヨムケ東校どっ知壁テケ禁去フミ人過を装5階がねぜ法逆はじ端40落ミ予竹マヘナセ任1悪た。省ぜりせ製暇ょへそけ風井イ劣手はぼまず郵富法く作断タオイ取座ゅょが出作ホシ月給26島ツチ皇面ユトクイ暮犯リワナヤ断連こうでつ蔭柔薄とレにの。演めけふぱ損田転10得観びトげぎ王物鉄夜がまけ理惜くち牡提づ車惑参ヘカユモ長臓超漫ぼドかわ。', + '모든 국민은 행위시의 법률에 의하여 범죄를 구성하지 아니하는 행위로 소추되지 아니하며. 전직대통령의 신분과 예우에 관하여는 법률로 정한다, 국회는 헌법 또는 법률에 특별한 규정이 없는 한 재적의원 과반수의 출석과 출석의원 과반수의 찬성으로 의결한다. 군인·군무원·경찰공무원 기타 법률이 정하는 자가 전투·훈련등 직무집행과 관련하여 받은 손해에 대하여는 법률이 정하는 보상외에 국가 또는 공공단체에 공무원의 직무상 불법행위로 인한 배상은 청구할 수 없다.', + 'كان فشكّل الشرقي مع, واحدة للمجهود تزامناً بعض بل. وتم جنوب للصين غينيا لم, ان وبدون وكسبت الأمور ذلك, أسر الخاسر الانجليزية هو. نفس لغزو مواقعها هو. الجو علاقة الصعداء انه أي, كما مع بمباركة للإتحاد الوزراء. ترتيب الأولى أن حدى, الشتوية باستحداث مدن بل, كان قد أوسع عملية. الأوضاع بالمطالبة كل قام, دون إذ شمال الربيع،. هُزم الخاصّة ٣٠ أما, مايو الصينية مع قبل.', + 'או סדר החול מיזמי קרימינולוגיה. קהילה בגרסה לויקיפדים אל היא, של צעד ציור ואלקטרוניקה. מדע מה ברית המזנון ארכיאולוגיה, אל טבלאות מבוקשים כלל. מאמרשיחהצפה העריכהגירסאות שכל אל, כתב עיצוב מושגי של. קבלו קלאסיים ב מתן. נבחרים אווירונאוטיקה אם מלא, לוח למנוע ארכיאולוגיה מה. ארץ לערוך בקרבת מונחונים או, עזרה רקטות לויקיפדים אחר גם.', + 'Лорем ლორემ अधिकांश 覧六子 八メル 모든 בקרבת 💮 😂 äggg 123€ 𝄞.' +]; + + +describe('StringToUtf32 decoder', () => { describe('full codepoint test', () => { it('0..65535', () => { const decoder = new StringToUtf32(); @@ -51,6 +75,15 @@ describe('StringToUtf32 Decoder', () => { } }); }); + it('test strings', () => { + const decoder = new StringToUtf32(); + const target = new Uint32Array(500); + for (let i = 0; i < TEST_STRINGS.length; ++i) { + const length = decoder.decode(TEST_STRINGS[i], target); + assert.equal(toString(target, length), TEST_STRINGS[i]); + decoder.clear(); + } + }); describe('stream handling', () => { it('surrogates mixed advance by 1', () => { const decoder = new StringToUtf32(); @@ -65,3 +98,102 @@ describe('StringToUtf32 Decoder', () => { }); }); }); + +describe('Utf8ToUtf32 decoder', () => { + describe('full codepoint test', () => { + it('0..65535 (1/2/3 byte sequences)', () => { + const decoder = new Utf8ToUtf32(); + const target = new Uint32Array(5); + for (let i = 0; i < 65536; ++i) { + // skip surrogate pairs + if (i >= 0xD800 && i <= 0xDFFF) { + continue; + } + const utf8Data = fromByteString(encode(String.fromCharCode(i))); + const length = decoder.decode(utf8Data, target); + assert.equal(length, 1); + assert.equal(toString(target, length), String.fromCharCode(i)); + decoder.clear(); + } + }); + it('65536..0x10FFFF (4 byte sequences)', function(): void { + this.timeout(20000); + const decoder = new Utf8ToUtf32(); + const target = new Uint32Array(5); + for (let i = 65536; i < 0x10FFFF; ++i) { + const utf8Data = fromByteString(encode(stringFromCodePoint(i))); + const length = decoder.decode(utf8Data, target); + assert.equal(length, 1); + assert.equal(target[0], i); + decoder.clear(); + } + }); + }); + it('test strings', () => { + const decoder = new Utf8ToUtf32(); + const target = new Uint32Array(500); + for (let i = 0; i < TEST_STRINGS.length; ++i) { + const utf8Data = fromByteString(encode(TEST_STRINGS[i])); + const length = decoder.decode(utf8Data, target); + assert.equal(toString(target, length), TEST_STRINGS[i]); + decoder.clear(); + } + }); + describe('stream handling', () => { + it('2 byte sequences - advance by 1', () => { + const decoder = new Utf8ToUtf32(); + const target = new Uint32Array(5); + const utf8Data = fromByteString('\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f\xc3\xb6\xc3\xa4\xc3\xbc'); + let decoded = ''; + for (let i = 0; i < utf8Data.length; ++i) { + const written = decoder.decode(utf8Data.slice(i, i + 1), target); + decoded += toString(target, written); + } + assert(decoded, 'ÄÖÜßöäü'); + }); + it('2/3 byte sequences - advance by 1', () => { + const decoder = new Utf8ToUtf32(); + const target = new Uint32Array(5); + const utf8Data = fromByteString('\xc3\x84\xe2\x82\xac\xc3\x96\xe2\x82\xac\xc3\x9c\xe2\x82\xac\xc3\x9f\xe2\x82\xac\xc3\xb6\xe2\x82\xac\xc3\xa4\xe2\x82\xac\xc3\xbc'); + let decoded = ''; + for (let i = 0; i < utf8Data.length; ++i) { + const written = decoder.decode(utf8Data.slice(i, i + 1), target); + decoded += toString(target, written); + } + assert(decoded, 'Āր܀߀ö€ä€ü'); + }); + it('2/3/4 byte sequences - advance by 1', () => { + const decoder = new Utf8ToUtf32(); + const target = new Uint32Array(5); + const utf8Data = fromByteString('\xc3\x84\xe2\x82\xac\xf0\x9d\x84\x9e\xc3\x96\xf0\x9d\x84\x9e\xe2\x82\xac\xc3\x9c\xf0\x9d\x84\x9e\xe2\x82\xac'); + let decoded = ''; + for (let i = 0; i < utf8Data.length; ++i) { + const written = decoder.decode(utf8Data.slice(i, i + 1), target); + decoded += toString(target, written); + } + assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€'); + }); + it('2/3/4 byte sequences - advance by 2', () => { + const decoder = new Utf8ToUtf32(); + const target = new Uint32Array(5); + const utf8Data = fromByteString('\xc3\x84\xe2\x82\xac\xf0\x9d\x84\x9e\xc3\x96\xf0\x9d\x84\x9e\xe2\x82\xac\xc3\x9c\xf0\x9d\x84\x9e\xe2\x82\xac'); + let decoded = ''; + for (let i = 0; i < utf8Data.length; i += 2) { + const written = decoder.decode(utf8Data.slice(i, i + 2), target); + decoded += toString(target, written); + } + assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€'); + }); + it('2/3/4 byte sequences - advance by 3', () => { + const decoder = new Utf8ToUtf32(); + const target = new Uint32Array(5); + const utf8Data = fromByteString('\xc3\x84\xe2\x82\xac\xf0\x9d\x84\x9e\xc3\x96\xf0\x9d\x84\x9e\xe2\x82\xac\xc3\x9c\xf0\x9d\x84\x9e\xe2\x82\xac'); + let decoded = ''; + for (let i = 0; i < utf8Data.length; i += 3) { + const written = decoder.decode(utf8Data.slice(i, i + 3), target); + decoded += toString(target, written); + } + assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€'); + }); + }); +}); diff --git a/src/core/input/TextDecoder.ts b/src/core/input/TextDecoder.ts index 04407a09..9080c09d 100644 --- a/src/core/input/TextDecoder.ts +++ b/src/core/input/TextDecoder.ts @@ -3,6 +3,19 @@ * @license MIT */ + +/** + * Polyfill - Convert UTF32 codepoint into JS string. + */ +export function stringFromCodePoint(codePoint: number): string { + if (codePoint > 0xFFFF) { + codePoint -= 0x10000; + return String.fromCharCode((codePoint >> 10) + 0xD800) + String.fromCharCode((codePoint % 0x400) + 0xDC00); + } + return String.fromCharCode(codePoint); +} + + /** * StringToUtf32 - decodes UTF16 sequences into UTF32 codepoints. * To keep the decoder in line with JS strings it handles single surrogates as UCS2. @@ -73,12 +86,232 @@ export class StringToUtf32 { } /** - * Polyfill - Convert UTF32 codepoint into JS string. + * Utf8Decoder - decodes UTF8 byte sequences into UTF32 codepoints. */ -export function stringFromCodePoint(codePoint: number): string { - if (codePoint > 0xFFFF) { - codePoint -= 0x10000; - return String.fromCharCode((codePoint >> 10) + 0xD800) + String.fromCharCode((codePoint % 0x400) + 0xDC00); +export class Utf8ToUtf32 { + public interim: Uint8Array = new Uint8Array(3); + + /** + * Clears interim bytes and resets decoder to clean state. + */ + public clear(): void { + this.interim.fill(0); + } + + /** + * Decodes UTF8 byte sequences in `input` to UTF32 codepoints in `target`. + * The methods assumes stream input and will store partly transmitted bytes + * and decode them with the next data chunk. + * Note: The method does no bound checks for target, therefore make sure + * the provided data chunk does not exceed the size of `target`. + * Returns the number of written codepoints in `target`. + */ + decode(input: Uint8Array, target: Uint32Array): number { + const length = input.length; + + if (!length) { + return 0; + } + + let size = 0; + let byte1; + let byte2; + let byte3; + let byte4; + let codepoint = 0; + let startPos = 0; + + // handle leftover bytes + if (this.interim[0]) { + let discardInterim = false; + let cp = this.interim[0]; + cp &= ((((cp & 0xE0) === 0xC0)) ? 0x1F : (((cp & 0xF0) === 0xE0)) ? 0x0F : 0x07); + let pos = 0; + let tmp; + while ((tmp = this.interim[++pos] & 0x3F) && pos < 4) { + cp <<= 6; + cp |= tmp; + } + // missing bytes - read ahead from input + const type = (((this.interim[0] & 0xE0) === 0xC0)) ? 2 : (((this.interim[0] & 0xF0) === 0xE0)) ? 3 : 4; + const missing = type - pos; + while (startPos < missing) { + if (startPos >= length) { + return 0; + } + tmp = input[startPos++]; + if ((tmp & 0xC0) !== 0x80) { + // wrong continuation, discard interim bytes completely + startPos--; + discardInterim = true; + break; + } else { + // need to save so we can continue short inputs in next call + this.interim[pos++] = tmp; + cp <<= 6; + cp |= tmp & 0x3F; + } + } + if (!discardInterim) { + // final test is type dependent + if (type === 2) { + if (cp < 0x80) { + // wrong starter byte + startPos--; + } else { + target[size++] = cp; + } + } else if (type === 3) { + if (cp < 0x0800 || (cp >= 0xD800 && cp <= 0xDFFF)) { + // illegal codepoint + } else { + target[size++] = cp; + } + } else { + if (codepoint < 0x010000 || codepoint > 0x10FFFF) { + // illegal codepoint + } else { + target[size++] = cp; + } + } + } + this.interim.fill(0); + } + + // loop through input + const fourStop = length - 4; + let i = startPos; + while (i < length) { + + /** + * ASCII shortcut with loop unrolled to 4 consecutive ASCII chars. + * This is a compromise between speed gain for ASCII + * and penalty for non ASCII: + * For best ASCII performance the char should be stored directly into target, + * but even a single attempt to write to target and compare afterwards + * penalizes non ASCII really bad (-50%), thus we load the char into byteX first, + * which reduces ASCII performance by ~15%. + * This trial for ASCII reduces non ASCII performance by ~10% which seems acceptible + * compared to the gains. + * Note that this optimization only takes place for 4 consecutive ASCII chars, + * for any shorter it bails out. Worst case - all 4 bytes being read but + * thrown away due to the last being a non ASCII char (-10% performance). + */ + while (i < fourStop + && !((byte1 = input[i]) & 0x80) + && !((byte2 = input[i + 1]) & 0x80) + && !((byte3 = input[i + 2]) & 0x80) + && !((byte4 = input[i + 3]) & 0x80)) + { + target[size++] = byte1; + target[size++] = byte2; + target[size++] = byte3; + target[size++] = byte4; + i += 4; + } + + // reread byte1 + byte1 = input[i++]; + + // 1 byte + if (byte1 < 0x80) { + target[size++] = byte1; + + // 2 bytes + } else if ((byte1 & 0xE0) === 0xC0) { + if (i >= length) { + this.interim[0] = byte1; + return size; + } + byte2 = input[i++]; + if ((byte2 & 0xC0) !== 0x80) { + // wrong continuation + i--; + continue; + } + codepoint = (byte1 & 0x1F) << 6 | (byte2 & 0x3F); + if (codepoint < 0x80) { + // wrong starter byte + i--; + continue; + } + target[size++] = codepoint; + + // 3 bytes + } else if ((byte1 & 0xF0) === 0xE0) { + if (i >= length) { + this.interim[0] = byte1; + return size; + } + byte2 = input[i++]; + if ((byte2 & 0xC0) !== 0x80) { + // wrong continuation + i--; + continue; + } + if (i >= length) { + this.interim[0] = byte1; + this.interim[1] = byte2; + return size; + } + byte3 = input[i++]; + if ((byte3 & 0xC0) !== 0x80) { + // wrong continuation + i--; + continue; + } + codepoint = (byte1 & 0x0F) << 12 | (byte2 & 0x3F) << 6 | (byte3 & 0x3F); + if (codepoint < 0x0800 || (codepoint >= 0xD800 && codepoint <= 0xDFFF)) { + // illegal codepoint, no i-- here + continue; + } + target[size++] = codepoint; + + // 4 bytes + } else if ((byte1 & 0xF8) === 0xF0) { + if (i >= length) { + this.interim[0] = byte1; + return size; + } + byte2 = input[i++]; + if ((byte2 & 0xC0) !== 0x80) { + // wrong continuation + i--; + continue; + } + if (i >= length) { + this.interim[0] = byte1; + this.interim[1] = byte2; + return size; + } + byte3 = input[i++]; + if ((byte3 & 0xC0) !== 0x80) { + // wrong continuation + i--; + continue; + } + if (i >= length) { + this.interim[0] = byte1; + this.interim[1] = byte2; + this.interim[2] = byte3; + return size; + } + byte4 = input[i++]; + if ((byte4 & 0xC0) !== 0x80) { + // wrong continuation + i--; + continue; + } + codepoint = (byte1 & 0x07) << 18 | (byte2 & 0x3F) << 12 | (byte3 & 0x3F) << 6 | (byte4 & 0x3F); + if (codepoint < 0x010000 || codepoint > 0x10FFFF) { + // illegal codepoint, no i-- here + continue; + } + target[size++] = codepoint; + } else { + // illegal byte, just skip + } + } + return size; } - return String.fromCharCode(codePoint); } From d7ea0edfcf27fdd3e37963b041fc647d035b0b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 20 Jan 2019 22:19:50 +0100 Subject: [PATCH 04/97] add utf8 input to terminal --- src/InputHandler.ts | 33 ++++++++++++++++++++++++++++++--- src/Terminal.ts | 17 +++++++++++++++++ src/Types.ts | 1 + src/public/Terminal.ts | 3 +++ src/ui/TestUtils.test.ts | 3 +++ typings/xterm.d.ts | 6 ++++++ 6 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index c41b4165..70491027 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -15,7 +15,7 @@ import { ICharset } from './core/Types'; import { IDisposable } from 'xterm'; import { Disposable } from './common/Lifecycle'; import { concat, utf32ToString } from './common/TypedArrayUtils'; -import { StringToUtf32, stringFromCodePoint } from './core/input/TextDecoder'; +import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from './core/input/TextDecoder'; import { CellData } from './BufferLine'; /** @@ -104,8 +104,9 @@ class DECRQSS implements IDcsHandler { * each function's header comment. */ export class InputHandler extends Disposable implements IInputHandler { - private _parseBuffer: Uint32Array = new Uint32Array(4096); - private _stringDecoder: StringToUtf32 = new StringToUtf32(); + private _parseBuffer = new Uint32Array(4096); + private _stringDecoder = new StringToUtf32(); + private _utf8Decoder = new Utf8ToUtf32(); private _cell: CellData = new CellData(); constructor( @@ -311,6 +312,32 @@ export class InputHandler extends Disposable implements IInputHandler { } } + public parseUtf8(data: Uint8Array): void { + // Ensure the terminal is not disposed + if (!this._terminal) { + return; + } + + let buffer = this._terminal.buffer; + const cursorStartX = buffer.x; + const cursorStartY = buffer.y; + + // TODO: Consolidate debug/logging #1560 + if ((this._terminal).debug) { + this._terminal.log('data: ' + data); + } + + if (this._parseBuffer.length < data.length) { + this._parseBuffer = new Uint32Array(data.length); + } + this._parser.parse(this._parseBuffer, this._utf8Decoder.decode(data, this._parseBuffer)); + + buffer = this._terminal.buffer; + if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) { + this._terminal.emit('cursormove'); + } + } + public print(data: Uint32Array, start: number, end: number): void { let code: number; let chWidth: number; diff --git a/src/Terminal.ts b/src/Terminal.ts index 33d8e60f..e92ef08b 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1301,6 +1301,23 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } } + /** + * Writes utf8 data to the terminal. + * TODO: This currently does no flow control. + */ + public writeUtf8(data: Uint8Array): void { + if (this._isDisposed) { + return; + } + this._refreshStart = this.buffer.y; + this._refreshEnd = this.buffer.y; + + this._inputHandler.parseUtf8(data); + + this.updateRange(this.buffer.y); + this.refresh(this._refreshStart, this._refreshEnd); + } + /** * Writes text to the terminal. * @param data The text to write to the terminal. diff --git a/src/Types.ts b/src/Types.ts index d176799f..186e2149 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -111,6 +111,7 @@ export interface ICompositionHelper { */ export interface IInputHandler { parse(data: string): void; + parseUtf8(data: Uint8Array): void; print(data: Uint32Array, start: number, end: number): void; /** C0 BEL */ bell(): void; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 87fcfaef..7e219e08 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -122,6 +122,9 @@ export class Terminal implements ITerminalApi { public write(data: string): void { this._core.write(data); } + public writeUtf8(data: Uint8Array): void { + this._core.writeUtf8(data); + } public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'rendererType' | 'termName'): string; public getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean; public getOption(key: 'colors'): string[]; diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index 9d525fbf..13abee76 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -99,6 +99,9 @@ export class MockTerminal implements ITerminal { write(data: string): void { throw new Error('Method not implemented.'); } + writeUtf8(data: Uint8Array): void { + throw new Error('Method not implemented.'); + } bracketedPasteMode: boolean; mouseHelper: IMouseHelper; renderer: IRenderer; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 11fab909..ffaba90e 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -636,6 +636,12 @@ declare module 'xterm' { */ write(data: string): void; + /** + * Writes UTF8 data to the terminal. + * @param data The data to write to the terminal. + */ + writeUtf8(data: Uint8Array): void; + /** * Retrieves an option's value from the terminal. * @param key The option key. From e6e5ecc0f2c4742e781b0f6c7b95caaaae6a024f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 20 Jan 2019 23:06:51 +0100 Subject: [PATCH 05/97] change demo to utf8 input --- demo/client.ts | 1 + demo/server.js | 13 +++++++------ src/addons/attach/attach.ts | 5 +++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index a3a912f6..a38b8a5c 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -134,6 +134,7 @@ function createTerminal(): void { pid = processId; socketURL += processId; socket = new WebSocket(socketURL); + socket.binaryType = 'arraybuffer'; socket.onopen = runRealTerminal; socket.onclose = runFakeTerminal; socket.onerror = runFakeTerminal; diff --git a/demo/server.js b/demo/server.js index 5ff9ca61..86bc9069 100644 --- a/demo/server.js +++ b/demo/server.js @@ -32,7 +32,8 @@ function startServer() { cols: cols || 80, rows: rows || 24, cwd: process.env.PWD, - env: process.env + env: process.env, + encoding: null }); console.log('Created terminal with PID: ' + term.pid); @@ -62,20 +63,20 @@ function startServer() { ws.send(logs[term.pid]); function buffer(socket, timeout) { - let s = ''; + let buffer = []; let sender = null; return (data) => { - s += data; + buffer.push(data); if (!sender) { sender = setTimeout(() => { - socket.send(s); - s = ''; + socket.send(Buffer.concat(buffer)); + buffer = []; sender = null; }, timeout); } }; } - const send = buffer(ws, 5); + const send = buffer(ws, 5); term.on('data', function(data) { try { diff --git a/src/addons/attach/attach.ts b/src/addons/attach/attach.ts index f121e2e2..7333f92b 100644 --- a/src/addons/attach/attach.ts +++ b/src/addons/attach/attach.ts @@ -42,6 +42,11 @@ export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean addonTerminal.__getMessage = function(ev: MessageEvent): void { let str: string; + if (ev.data instanceof ArrayBuffer) { + addonTerminal.writeUtf8(new Uint8Array(ev.data)); + return; + } + if (typeof ev.data === 'object') { if (!myTextDecoder) { myTextDecoder = new TextDecoder(); From 3f993d675f304a3587677dfe8536a763d579a70e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Apr 2019 20:17:33 -0400 Subject: [PATCH 06/97] Fix conflicts with new stuff --- src/public/Terminal.test.ts | 17 ----------------- src/tsconfig.all.json | 3 --- 2 files changed, 20 deletions(-) delete mode 100644 src/public/Terminal.test.ts diff --git a/src/public/Terminal.test.ts b/src/public/Terminal.test.ts deleted file mode 100644 index 06c8f1d5..00000000 --- a/src/public/Terminal.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) 2016 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { Terminal } from './Terminal'; -import * as attach from '../addons/attach/attach'; - -describe('Terminal', () => { - it('should apply addons with Terminal.applyAddon', () => { - Terminal.applyAddon(attach); - // Test that addon was applied successfully, adding attach to Terminal's - // prototype. - assert.equal(typeof (Terminal).prototype.attach, 'function'); - }); -}); diff --git a/src/tsconfig.all.json b/src/tsconfig.all.json index bee5df32..d0bf01a0 100644 --- a/src/tsconfig.all.json +++ b/src/tsconfig.all.json @@ -3,14 +3,11 @@ "include": [], "references": [ { "path": "." }, - { "path": "./addons/attach" }, { "path": "./addons/fit" }, { "path": "./addons/fullscreen" }, { "path": "./addons/search" }, { "path": "./addons/terminado" }, - { "path": "./addons/webLinks" }, { "path": "./addons/winptyCompat" }, { "path": "./addons/zmodem" } ] } - \ No newline at end of file From 26a80d0d3509dd7e997df7cf2ebdbc8e522ba39b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 7 Apr 2019 14:53:09 -0400 Subject: [PATCH 07/97] Convert to much simpler model --- demo/client.ts | 5 ++- demo/start.js | 1 + package.json | 4 +- src/Terminal.ts | 14 ++----- src/public/Terminal.ts | 12 ++---- src/ui/AddonManager.test.ts | 73 ++++--------------------------------- src/ui/AddonManager.ts | 36 ++++++------------ src/ui/TestUtils.test.ts | 10 +---- typings/xterm.d.ts | 18 ++++----- yarn.lock | 16 ++++---- 10 files changed, 49 insertions(+), 140 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 3d55bc00..f4a23f52 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -88,8 +88,9 @@ function createTerminal(): void { // Load addons const typedTerm = term as TerminalType; - typedTerm.loadAddon(WebLinksAddon).init(); - attachAddon = typedTerm.loadAddon(AttachAddon); + typedTerm.loadAddon(new WebLinksAddon()); + attachAddon = new AttachAddon(); + typedTerm.loadAddon(attachAddon); window.term = term; // Expose `term` to window for debugging purposes term.on('resize', (size: { cols: number, rows: number }) => { diff --git a/demo/start.js b/demo/start.js index 2e2186dc..7e13e790 100644 --- a/demo/start.js +++ b/demo/start.js @@ -31,6 +31,7 @@ const clientConfig = { ] }, resolve: { + modules: [path.resolve(__dirname, '..'), 'node_modules'], extensions: [ '.tsx', '.ts', '.js' ] }, output: { diff --git a/package.json b/package.json index 438e3e1b..b1f8e8f4 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,8 @@ "vinyl-source-stream": "^1.1.0", "webpack": "^4.17.1", "webpack-cli": "^3.1.0", - "xterm-addon-attach": "0.1.0-beta4", - "xterm-addon-web-links": "0.1.0-beta3", + "xterm-addon-attach": "0.1.0-beta7", + "xterm-addon-web-links": "0.1.0-beta6", "zmodem.js": "^0.1.5" }, "scripts": { diff --git a/src/Terminal.ts b/src/Terminal.ts index df8f5d90..7a29a282 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -44,7 +44,7 @@ import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager'; import { MouseZoneManager } from './ui/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './ui/ScreenDprMonitor'; -import { ITheme, IMarker, IDisposable, ITerminalAddon, ITerminalAddonConstructor } from 'xterm'; +import { ITheme, IMarker, IDisposable, ITerminalAddon } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; @@ -1925,16 +1925,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // this.options.bellStyle === 'both'; } - public loadAddon(addonConstructor: ITerminalAddonConstructor): T { - return this._addonManager.loadAddon(this, addonConstructor); - } - - public disposeAddon(addonConstructor: ITerminalAddonConstructor): void { - this._addonManager.disposeAddon(addonConstructor); - } - - public getAddon(addonConstructor: ITerminalAddonConstructor): T { - return this._addonManager.getAddon(addonConstructor); + public loadAddon(addon: ITerminalAddon): void { + return this._addonManager.loadAddon(this, addon); } } diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 6f2de10f..77889271 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ITerminalAddonConstructor } from 'xterm'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon } from 'xterm'; import { ITerminal } from '../Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; @@ -154,14 +154,8 @@ export class Terminal implements ITerminalApi { public static applyAddon(addon: any): void { addon.apply(Terminal); } - public loadAddon(addonConstructor: ITerminalAddonConstructor): T { - return this._core.loadAddon(addonConstructor); - } - public getAddon(addonConstructor: ITerminalAddonConstructor): T { - return this._core.getAddon(addonConstructor); - } - public disposeAddon(addonConstructor: ITerminalAddonConstructor): void { - this._core.disposeAddon(addonConstructor); + public loadAddon(addon: ITerminalAddon): void { + return this._core.loadAddon(addon); } public static get strings(): ILocalizableStrings { return Strings; diff --git a/src/ui/AddonManager.test.ts b/src/ui/AddonManager.test.ts index ac83917c..8198ce31 100644 --- a/src/ui/AddonManager.test.ts +++ b/src/ui/AddonManager.test.ts @@ -24,84 +24,27 @@ describe('AddonManager', () => { it('should call addon constructor', () => { let called = false; class Addon implements ITerminalAddon { - constructor(terminal: any) { + activate(terminal: any): void { assert.equal(terminal, 'foo', 'The first constructor arg should be Terminal'); called = true; } dispose(): void { } } - manager.loadAddon('foo' as any, Addon); + manager.loadAddon('foo' as any, new Addon()); assert.equal(called, true); }); }); - describe('getAddon', () => { - it('should fetch registered addons', () => { - class BaseAddon implements ITerminalAddon { - constructor() { } - dispose(): void { } - } - class Addon1 extends BaseAddon { } - class Addon2 extends BaseAddon { } - class Addon3 extends BaseAddon { } - const addon1 = manager.loadAddon(null, Addon1); - assert.equal(manager.getAddon(Addon1), addon1); - assert.equal(manager.addons.length, 1); - const addon2 = manager.loadAddon(null, Addon2); - assert.equal(manager.getAddon(Addon1), addon1); - assert.equal(manager.getAddon(Addon2), addon2); - assert.equal(manager.addons.length, 2); - const addon3 = manager.loadAddon(null, Addon3); - assert.equal(manager.getAddon(Addon1), addon1); - assert.equal(manager.getAddon(Addon2), addon2); - assert.equal(manager.getAddon(Addon3), addon3); - assert.equal(manager.addons.length, 3); - }); - }); - - describe('disposeAddon', () => { - it('should dispose the loaded addon and remove it from the loaded list', () => { - let called = 0; - class BaseAddon implements ITerminalAddon { - constructor() { } - dispose(): void { - called++; - } - } - class Addon1 extends BaseAddon { } - class Addon2 extends BaseAddon { } - class Addon3 extends BaseAddon { } - manager.loadAddon(null, Addon1); - manager.loadAddon(null, Addon2); - manager.loadAddon(null, Addon3); - assert.equal(manager.addons.length, 3); - manager.disposeAddon(Addon1); - assert.equal(called, 1); - assert.equal(manager.addons.length, 2); - manager.disposeAddon(Addon2); - assert.equal(called, 2); - assert.equal(manager.addons.length, 1); - manager.disposeAddon(Addon3); - assert.equal(called, 3); - assert.equal(manager.addons.length, 0); - }); - }); - describe('dispose', () => { it('should dispose all loaded addons', () => { let called = 0; - class BaseAddon implements ITerminalAddon { - constructor() { } - dispose(): void { - called++; - } + class Addon implements ITerminalAddon { + activate(): void {} + dispose(): void { called++; } } - class Addon1 extends BaseAddon { } - class Addon2 extends BaseAddon { } - class Addon3 extends BaseAddon { } - manager.loadAddon(null, Addon1); - manager.loadAddon(null, Addon2); - manager.loadAddon(null, Addon3); + manager.loadAddon(null, new Addon()); + manager.loadAddon(null, new Addon()); + manager.loadAddon(null, new Addon()); assert.equal(manager.addons.length, 3); manager.dispose(); assert.equal(called, 3); diff --git a/src/ui/AddonManager.ts b/src/ui/AddonManager.ts index b55bd6a8..34e7e5dd 100644 --- a/src/ui/AddonManager.ts +++ b/src/ui/AddonManager.ts @@ -3,12 +3,12 @@ * @license MIT */ -import { ITerminalAddon, ITerminalAddonConstructor, IDisposable, Terminal } from 'xterm'; +import { ITerminalAddon, IDisposable, Terminal } from 'xterm'; export interface ILoadedAddon { - ctor: ITerminalAddonConstructor; instance: ITerminalAddon; dispose: () => void; + isDisposed: boolean; } export class AddonManager implements IDisposable { @@ -23,38 +23,25 @@ export class AddonManager implements IDisposable { } } - public loadAddon(terminal: Terminal, addonConstructor: ITerminalAddonConstructor): T { - const instance = new addonConstructor(terminal); + public loadAddon(terminal: Terminal, instance: ITerminalAddon): void { const loadedAddon: ILoadedAddon = { - ctor: addonConstructor, instance, - dispose: instance.dispose + dispose: instance.dispose, + isDisposed: false }; this._addons.push(loadedAddon); instance.dispose = () => this._wrappedAddonDispose(loadedAddon); - return instance; - } - - public disposeAddon(addonConstructor: ITerminalAddonConstructor): void { - const match = this._addons.find(value => value.ctor === addonConstructor); - if (!match) { - throw new Error('Could not dispose an addon that has not been loaded'); - } - match.instance.dispose(); - } - - public getAddon(addonConstructor: ITerminalAddonConstructor): T { - const match = this._addons.find(value => value.ctor === addonConstructor); - if (!match) { - return undefined; - } - return match.instance as T; + instance.activate(terminal); } private _wrappedAddonDispose(loadedAddon: ILoadedAddon): void { + if (loadedAddon.isDisposed) { + // Do nothing if already disposed + return; + } let index = -1; for (let i = 0; i < this._addons.length; i++) { - if (this._addons[i].ctor === loadedAddon.ctor) { + if (this._addons[i] === loadedAddon) { index = i; break; } @@ -63,6 +50,7 @@ export class AddonManager implements IDisposable { throw new Error('Could not dispose an addon that has not been loaded'); } loadedAddon.dispose(); + loadedAddon.isDisposed = true; this._addons.splice(index, 1); } } diff --git a/src/ui/TestUtils.test.ts b/src/ui/TestUtils.test.ts index ec3aa60e..4a3a24e3 100644 --- a/src/ui/TestUtils.test.ts +++ b/src/ui/TestUtils.test.ts @@ -8,7 +8,7 @@ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuff import { ICircularList, XtermListener } from '../common/Types'; import { Buffer } from '../Buffer'; import * as Browser from '../common/Platform'; -import { ITheme, IDisposable, IMarker, ITerminalAddon, ITerminalAddonConstructor } from 'xterm'; +import { ITheme, IDisposable, IMarker, ITerminalAddon } from 'xterm'; import { Terminal } from '../Terminal'; import { AttributeData } from '../BufferLine'; @@ -20,13 +20,7 @@ export class TestTerminal extends Terminal { } export class MockTerminal implements ITerminal { - loadAddon(addonConstructor: ITerminalAddonConstructor): T { - throw new Error('Method not implemented.'); - } - disposeAddon(addonConstructor: ITerminalAddonConstructor): void { - throw new Error('Method not implemented.'); - } - getAddon(addonConstructor: ITerminalAddonConstructor): T { + loadAddon(addon: ITerminalAddon): void { throw new Error('Method not implemented.'); } markers: IMarker[]; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index fddd8982..830ed298 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -772,22 +772,18 @@ declare module 'xterm' { */ static applyAddon(addon: any): void; - loadAddon(addonConstructor: ITerminalAddonConstructor): T; - disposeAddon(addonConstructor: ITerminalAddonConstructor): void; - getAddon(addonConstructor: ITerminalAddonConstructor): T; - } - - export interface ITerminalAddonConstructor { - new(terminal: Terminal): T; + /** + * Loads an addon into this instance of xterm.js. + * @param addon The addon to load. + */ + loadAddon(addon: ITerminalAddon): void; } export interface ITerminalAddon { /** - * This property declares all addon dependencies that must be intialized - * before this addon can be constructed. For addons with no dependencies - * just don't include this property. + * This is called when the addon is activated within xterm.js. */ - // readonly DEPENDENCIES?: ITerminalAddonConstructor[]; + activate(terminal: Terminal): void; /** * This function includes anything that needs to happen to clean up when diff --git a/yarn.lock b/yarn.lock index 1fa1739c..4ca95193 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7122,15 +7122,15 @@ xregexp@4.0.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= -xterm-addon-attach@0.1.0-beta4: - version "0.1.0-beta4" - resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.1.0-beta4.tgz#546010f66533f22bfad7605345e44ed95d90a1de" - integrity sha512-HwxNoNS1Fxoo6+MPZJ+5+sMTQHrZEcptL4qstHlaERqxL7ei/lvKMpSRfIo1eRNqxL4vHzYkNWJO7QLATmdalA== +xterm-addon-attach@0.1.0-beta7: + version "0.1.0-beta7" + resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.1.0-beta7.tgz#787f6cce709611ee08ab731b95a62fa1c0bce6a9" + integrity sha512-nQr6LcYtpZcyDoHyL/BDIPJcTgL7qlHR/rvm8lSizQysGVT0pSzr5M7SjY3kQHw33U3hTer3c6oZzwjfj4ohOw== -xterm-addon-web-links@0.1.0-beta3: - version "0.1.0-beta3" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta3.tgz#bd2d45d399340bd1b5bbf44850a0be9c91a1451e" - integrity sha512-nkgwAYZXS97zL650MTl6RnA/iXYAD0yOVf/28+ZTlLQZJVYH2DP22rhOK0aqO+tWX91WUrvkcqxFCY965fomaQ== +xterm-addon-web-links@0.1.0-beta6: + version "0.1.0-beta6" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta6.tgz#9b4e862be8928ef455a667745bea479665db6c6b" + integrity sha512-tkVU5wCfBFjXwfOvcbMHoLoMDANztkwSREiKyu2R059kEF+sP67Z33HzxVCXUWFuCmutcx40xR2O0BK68gXZlg== y18n@^3.2.0, y18n@^3.2.1: version "3.2.1" From 267071ead65b1858403bce3baa8db28ab535f0aa Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 7 Apr 2019 15:00:27 -0400 Subject: [PATCH 08/97] Add deprecation message to applyAddon --- typings/xterm.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 830ed298..1d1ebad9 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -769,6 +769,7 @@ declare module 'xterm' { * Applies an addon to the Terminal prototype, making it available to all * newly created Terminals. * @param addon The addon to apply. + * @deprecated Use the new loadAddon API/addon format. */ static applyAddon(addon: any): void; From efeca49705e12676ba3c9a85b615576f552cf882 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 7 Apr 2019 15:29:37 -0400 Subject: [PATCH 09/97] Mostly implemented buffer API Part of #1994 --- src/public/Terminal.ts | 32 +++++++++++++++-- typings/xterm.d.ts | 80 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 87fcfaef..bc82d235 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings } from 'xterm'; -import { ITerminal } from '../Types'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm'; +import { ITerminal, IBufferLine, IBuffer } from '../Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; @@ -19,6 +19,7 @@ export class Terminal implements ITerminalApi { public get textarea(): HTMLTextAreaElement { return this._core.textarea; } public get rows(): number { return this._core.rows; } public get cols(): number { return this._core.cols; } + public get buffer(): IBufferApi { return new BufferApiView(this._core.buffer); } public get markers(): IMarker[] { return this._core.markers; } public blur(): void { this._core.blur(); @@ -158,3 +159,30 @@ export class Terminal implements ITerminalApi { return Strings; } } + +class BufferApiView implements IBufferApi { + constructor(private _buffer: IBuffer) {} + + public get cursorY(): number { return this._buffer.y; } + public get cursorX(): number { return this._buffer.x; } + public get viewportY(): number { return this._buffer.ydisp; } + public get baseY(): number { return this._buffer.ybase; } + public get length(): number { return this._buffer.lines.length; } + public getLine(y: number): IBufferLineApi { return new BufferLineApiView(this._buffer.lines.get(y)); } +} + +class BufferLineApiView implements IBufferLineApi { + constructor(private _line: IBufferLine) {} + + public get isWrapped(): boolean { return this._line.isWrapped; } + public getCell(x: number): IBufferCellApi { return new BufferCellApiView(this._line, x); } + public translateToString(trimRight: boolean, startColumn: number, endColumn: number): string { + return this._line.translateToString(trimRight, startColumn, endColumn); + } +} + +class BufferCellApiView implements IBufferCellApi { + constructor(private _line: IBufferLine, private _x: number) {} + public get char(): string { return this._line.getString(this._x); } + public get width(): number { return this._line.getWidth(this._x); } +} diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index ec647a10..429ad7e3 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -347,6 +347,13 @@ declare module 'xterm' { */ readonly cols: number; + /** + * (EXPERIMENTAL) The terminal's current buffer, note that this might be + * either the normal buffer or the alt buffer depending on what's running in + * the terminal. + */ + readonly buffer: IBuffer; + /** * (EXPERIMENTAL) Get all markers registered against the buffer. If the alt * buffer is active this will always return []. @@ -772,4 +779,77 @@ declare module 'xterm' { */ static applyAddon(addon: any): void; } + + interface IBuffer { + /** + * The y position of the cursor. This ranges between `0` (when the + * cursor is at baseY) and `Terminal.rows - 1` (when the cursor is on the + * last row). + */ + readonly cursorY: number; + + /** + * The x position of the cursor. This ranges between `0` (left side) and + * `Terminal.cols - 1` (right side). + */ + readonly cursorX: number; + + /** + * The line within the buffer where the top of the viewport is. + */ + readonly viewportY: number; + + /** + * The line within the buffer where the top of the bottom page is (when + * fully scrolled down); + */ + readonly baseY: number; + + /** + * The amount of lines in the buffer. + */ + readonly length: number; + + /** + * Gets a line from the buffer. + * + * @param y The line index to get. + */ + getLine(y: number): IBufferLine; + } + + interface IBufferLine { + /** + * Whether the line is wrapped from the previous line. + */ + readonly isWrapped: boolean; + + /** + * Gets a cell from the line. + * + * @param x The character index to get. + */ + getCell(x: number): IBufferCell; + + /** + * Gets the line + */ + translateToString(trimRight: boolean, startColumn: number, endColumn: number): string; + } + + interface IBufferCell { + /** + * The character within the cell. + */ + readonly char: string; + + /** + * The width of the character. Some examples: + * + * - This is `1` for most cells. + * - This is `2` for wide character like CJK glyphs. + * - This is `0` for cells immediately following cells with a width of `2`. + */ + readonly width: number; + } } From b55bedc6af73f2eeeb0472f4b0699ceba3e59a90 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 21:29:08 -0700 Subject: [PATCH 10/97] Add terminal API interface to Types.ts This is needed since the API/public buffer now differs from the TerminalCore's --- src/Types.ts | 43 +++++++++++++++++++++++++++++++-- src/renderer/dom/DomRenderer.ts | 4 +-- typings/xterm.d.ts | 2 +- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/Types.ts b/src/Types.ts index 6e0108ae..1fa78824 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable } from 'xterm'; +import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker } from 'xterm'; import { IColorSet, IRenderer } from './renderer/Types'; import { IMouseZoneManager } from './ui/Types'; import { ICharset } from './core/Types'; @@ -202,7 +202,7 @@ export interface ILinkHoverEvent { fg: number; } -export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor { +export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor { screenElement: HTMLElement; selectionManager: ISelectionManager; charMeasure: ICharMeasure; @@ -231,6 +231,45 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce showCursor(): void; } +export interface IPublicTerminal extends IDisposable, IEventEmitter { + textarea: HTMLTextAreaElement; + rows: number; + cols: number; + buffer: IBuffer; + markers: IMarker[]; + blur(): void; + focus(): void; + resize(columns: number, rows: number): void; + writeln(data: string): void; + open(parent: HTMLElement): void; + attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; + addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable; + addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; + registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number; + deregisterLinkMatcher(matcherId: number): void; + registerCharacterJoiner(handler: (text: string) => [number, number][]): number; + deregisterCharacterJoiner(joinerId: number): void; + addMarker(cursorYOffset: number): IMarker; + hasSelection(): boolean; + getSelection(): string; + clearSelection(): void; + selectAll(): void; + selectLines(start: number, end: number): void; + dispose(): void; + destroy(): void; + scrollLines(amount: number): void; + scrollPages(pageCount: number): void; + scrollToTop(): void; + scrollToBottom(): void; + scrollToLine(line: number): void; + clear(): void; + write(data: string): void; + getOption(key: string): any; + setOption(key: string, value: any): void; + refresh(start: number, end: number): void; + reset(): void; +} + export interface IBufferAccessor { buffer: IBuffer; } diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 78ccc620..c4da6710 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -151,8 +151,8 @@ export class DomRenderer extends EventEmitter implements IRenderer { `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` + ` color: ${this.colorManager.colors.foreground.css};` + ` background-color: ${this.colorManager.colors.background.css};` + - ` font-family: ${this._terminal.getOption('fontFamily')};` + - ` font-size: ${this._terminal.getOption('fontSize')}px;` + + ` font-family: ${this._terminal.options.fontFamily};` + + ` font-size: ${this._terminal.options.fontSize}px;` + `}`; // Text styles styles += diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 429ad7e3..3a656eab 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -275,7 +275,7 @@ declare module 'xterm' { * A callback that fires when the mouse leaves a link. Note that this can * happen even when tooltipCallback hasn't fired for the link yet. */ - leaveCallback?: (event: MouseEvent, uri: string) => boolean | void; + leaveCallback?: () => void; /** * The priority of the link matcher, this defines the order in which the link From 5362ea195ce81ce2707540473c5b7d9d7f4f0eba Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 10 Apr 2019 00:24:50 -0700 Subject: [PATCH 11/97] Add first API test --- src/Terminal.integration.ts | 30 ++++++++++++++++++++++++++++++ src/public/Terminal.ts | 2 +- typings/xterm.d.ts | 2 +- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index 10043006..e0dad377 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -13,6 +13,7 @@ import * as path from 'path'; import * as pty from 'node-pty'; import { assert } from 'chai'; import { Terminal } from './Terminal'; +import { Terminal as PublicTerminal } from './public/Terminal'; import { WHITESPACE_CELL_CHAR } from './Buffer'; import { IViewport } from './Types'; import { CellData } from './BufferLine'; @@ -78,6 +79,35 @@ function terminalToString(term: Terminal): string { return result; } +describe('API tests', () => { + let api: PublicTerminal; + let core: TestTerminal; + + // expect files need terminal at 80x25! + const INIT_COLS = 80; + const INIT_ROWS = 25; + + beforeEach(() => { + api = new PublicTerminal({ cols: INIT_COLS, rows: INIT_ROWS }); + core = (api as any)._core; + core.innerWrite = () => (core as any)._innerWrite(); + core.refresh = () => {}; + core.viewport = { + syncScrollArea: () => {} + }; + }); + + describe('buffer', () => { + it('IBufferLine.getLine', () => { + core.writeBuffer.push('abc\n\rdef'); + core.innerWrite(); + assert.equal(api.buffer.length, INIT_ROWS); + assert.equal(api.buffer.getLine(0).translateToString(true), 'abc'); + assert.equal(api.buffer.getLine(1).translateToString(true), 'def'); + }); + }); +}); + // Skip tests on Windows since pty.open isn't supported if (os.platform() !== 'win32') { const consoleLog = console.log; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index bc82d235..35423b80 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -176,7 +176,7 @@ class BufferLineApiView implements IBufferLineApi { public get isWrapped(): boolean { return this._line.isWrapped; } public getCell(x: number): IBufferCellApi { return new BufferCellApiView(this._line, x); } - public translateToString(trimRight: boolean, startColumn: number, endColumn: number): string { + public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string { return this._line.translateToString(trimRight, startColumn, endColumn); } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 3a656eab..7f87cb12 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -834,7 +834,7 @@ declare module 'xterm' { /** * Gets the line */ - translateToString(trimRight: boolean, startColumn: number, endColumn: number): string; + translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string; } interface IBufferCell { From 4453a1cbf7b2f539956a60898876b341fd717788 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 28 Apr 2019 03:52:08 +0000 Subject: [PATCH 12/97] Automatically release stable builds Part of #1921 --- bin/publish.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/bin/publish.js b/bin/publish.js index ec7bf173..d5e5047e 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -11,9 +11,12 @@ const packageJson = require('../package.json'); // Setup auth fs.writeFileSync(`${process.env['HOME']}/.npmrc`, `//registry.npmjs.org/:_authToken=${process.env['NPM_AUTH_TOKEN']}`); -// Get the version -const tag = 'beta' -const nextVersion = getNextVersion(tag); +// Determine if this is a stable or beta release +const publishedVersions = getPublishedVersions(); +const isStableRelease = publishedVersions.indexOf(packageJson.version) === -1; + +// Get the next version +let nextVersion = isStableRelease ? packageJson.version : getNextBetaVersion(); console.log(`Publishing version: ${nextVersion}`); // Set the version in package.json @@ -22,16 +25,19 @@ packageJson.version = nextVersion; fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2)); // Publish -const result = cp.spawn('npm', ['publish', '--tag', tag], { - stdio: 'inherit' -}); +const args = ['publish']; +if (!isStableRelease) { + args.push('--tag', 'beta'); +} +const result = cp.spawn('npm', args, { stdio: 'inherit' }); result.on('exit', code => process.exit(code)); -function getNextVersion(tag) { +function getNextBetaVersion() { if (!/^[0-9]+\.[0-9]+\.[0-9]+$/.exec(packageJson.version)) { console.error('The package.json version must be of the form x.y.z'); process.exit(1); } + const tag = 'beta'; const stableVersion = packageJson.version.split('.'); const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.${stableVersion[2]}`; const publishedVersions = getPublishedVersions(nextStableVersion, tag); @@ -46,5 +52,8 @@ function getNextVersion(tag) { function getPublishedVersions(version, tag) { const versionsProcess = cp.spawnSync('npm', ['view', 'xterm', 'versions', '--json']); const versionsJson = JSON.parse(versionsProcess.stdout); - return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}[0-9]+`))); + if (tag) { + return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}[0-9]+`))); + } + return versionsJson; } From f0c3bab156ea5f32f0d824215ff86b14837ed3a9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 29 Apr 2019 11:35:42 -0700 Subject: [PATCH 13/97] Add build and test task --- .vscode/tasks.json | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .vscode/tasks.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..2ff9f3cb --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,48 @@ +{ + "version": "2.0.0", + "presentation": { + "echo": false, + "reveal": "always", + "focus": false, + "panel": "dedicated", + "showReuseMessage": true + }, + "tasks": [ + { + "type": "npm", + "script": "test", + "group": "test", + "problemMatcher": [] + }, + { + "type": "npm", + "script": "watch", + "group": "build", + "isBackground": true, + "problemMatcher": [], + "presentation": { + "group": "vscode" + } + }, + { + "type": "npm", + "script": "start", + "group": "build", + "isBackground": true, + "problemMatcher": [], + "presentation": { + "group": "vscode" + } + }, + { + "label": "Start demo", + "dependsOn": ["npm: watch", "npm: start"], + "group": "build", + "isBackground": true, + "problemMatcher": [], + "presentation": { + "group": "vscode" + } + } + ] +} From 4fd9b3f249ae7788ff0664f76f875b3202fdced4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 30 Apr 2019 00:58:18 +0200 Subject: [PATCH 14/97] jix joined cell representation as overloaded CellData type --- src/renderer/BaseRenderLayer.ts | 5 +-- src/renderer/CharacterJoinerRegistry.ts | 47 +++++++++++++++++++++++-- src/renderer/TextRenderLayer.ts | 14 ++++---- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index d851b2f2..00b38dcd 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -10,6 +10,7 @@ import BaseCharAtlas from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { CellData, AttributeData } from '../BufferLine'; import { WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../Buffer'; +import { JoinedCellData } from './CharacterJoinerRegistry'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -260,8 +261,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ protected drawChars(terminal: ITerminal, cell: ICellData, x: number, y: number): void { - // skip cache right away if we draw in RGB - if (cell.isFgRGB() || cell.isBgRGB()) { + // skip cache right away if we draw in RGB or have joined cells + if (cell.isFgRGB() || cell.isBgRGB() || cell instanceof JoinedCellData) { this._drawUncachedChars(terminal, cell, x, y); return; } diff --git a/src/renderer/CharacterJoinerRegistry.ts b/src/renderer/CharacterJoinerRegistry.ts index 8c521dd9..6a47ee22 100644 --- a/src/renderer/CharacterJoinerRegistry.ts +++ b/src/renderer/CharacterJoinerRegistry.ts @@ -1,8 +1,51 @@ -import { ITerminal, IBufferLine } from '../Types'; +import { ITerminal, IBufferLine, ICellData, CharData } from '../Types'; import { ICharacterJoinerRegistry, ICharacterJoiner } from './Types'; -import { CellData } from '../BufferLine'; +import { CellData, Content } from '../BufferLine'; import { WHITESPACE_CELL_CHAR } from '../Buffer'; +export class JoinedCellData extends CellData implements ICellData { + private _width: number = 0; + private _code: number = 0x1FFFFF; // highest allowed codepoint, meant as -1 + + public content: number = 0; + public fg: number = 0; + public bg: number = 0; + public combinedData: string = ''; + + constructor(firstCell: ICellData, chars: string, width: number) { + super(); + this.fg = firstCell.fg; + this.bg = firstCell.bg; + this.combinedData = chars; + this._width = width; + } + + public isCombined(): number { + // always mark joined cell data as combined + return Content.IS_COMBINED_MASK; + } + + public getWidth(): number { + return this._width; + } + + public getChars(): string { + return this.combinedData; + } + + public getCode(): number { + return this._code; + } + + public setFromCharData(value: CharData): void { + throw new Error('not implemented'); + } + + public getAsCharData(): CharData { + return [this.fg, this.getChars(), this.getWidth(), this.getCode()]; + } +} + export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { private _characterJoiners: ICharacterJoiner[] = []; diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index c4bdf19f..d54008b8 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -9,6 +9,7 @@ import { CharData, ITerminal, ICellData } from '../Types'; import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; import { CellData, AttributeData, Content } from '../BufferLine'; +import { JoinedCellData } from './CharacterJoinerRegistry'; /** * This CharData looks like a null character, which will forc a clear and render @@ -89,15 +90,12 @@ export class TextRenderLayer extends BaseRenderLayer { // We already know the exact start and end column of the joined range, // so we get the string and width representing it directly - cell = CellData.fromCharData([ - 0, + + cell = new JoinedCellData( + this._workCell, line.translateToString(true, range[0], range[1]), - range[1] - range[0], - 0xFFFFFF - ]); - // hacky: patch attrs - cell.fg = this._workCell.fg; - cell.bg = this._workCell.bg; + range[1] - range[0] + ); // Skip over the cells occupied by this range in the loop lastCharX = range[1] - 1; From 469d5158a01d2d8bb5b18bc4066ba77c3cda581b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 30 Apr 2019 19:19:41 +0000 Subject: [PATCH 15/97] Make Terminal.markers readonly --- typings/xterm.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 24a30b25..b6d1e320 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -359,7 +359,7 @@ declare module 'xterm' { * (EXPERIMENTAL) Get all markers registered against the buffer. If the alt * buffer is active this will always return []. */ - readonly markers: IMarker[]; + readonly markers: ReadonlyArray; /** * Natural language strings that can be localized. From f416bd155a7e40f555c403431e828b37f7b9e1d6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 2 May 2019 07:53:28 -0700 Subject: [PATCH 16/97] Make compound task the default --- .vscode/tasks.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2ff9f3cb..2f752faf 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -37,7 +37,10 @@ { "label": "Start demo", "dependsOn": ["npm: watch", "npm: start"], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "isBackground": true, "problemMatcher": [], "presentation": { From ea09c6af09f89c3a2a1a6886a7daf9d3e4a79ea0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 2 May 2019 09:23:13 -0700 Subject: [PATCH 17/97] Fix build --- src/public/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index d05a4f10..70ce7db4 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -30,7 +30,7 @@ export class Terminal implements ITerminalApi { public get textarea(): HTMLTextAreaElement { return this._core.textarea; } public get rows(): number { return this._core.rows; } public get cols(): number { return this._core.cols; } - public get markers(): IMarker[] { return this._core.markers; } + public get markers(): ReadonlyArray { return this._core.markers; } public blur(): void { this._core.blur(); } From 23eed1b3ea169fee0e1739fdd712bb256ff4892e Mon Sep 17 00:00:00 2001 From: roottool Date: Sat, 4 May 2019 02:02:26 +0900 Subject: [PATCH 18/97] Fix: Changed pathClause --- src/addons/webLinks/webLinks.test.ts | 166 +++++++++++++++++++++------ src/addons/webLinks/webLinks.ts | 3 +- 2 files changed, 133 insertions(+), 36 deletions(-) diff --git a/src/addons/webLinks/webLinks.test.ts b/src/addons/webLinks/webLinks.test.ts index 1e8a4ae7..1423e010 100644 --- a/src/addons/webLinks/webLinks.test.ts +++ b/src/addons/webLinks/webLinks.test.ts @@ -28,63 +28,159 @@ describe('webLinks addon', () => { }); }); - it('should allow ~ character in URI path', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); + describe('should allow simple URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); - const row = ' http://foo.com/a~b#c~d?e~f '; + const row = ' http://foo.com '; - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; - assert.equal(uri, 'http://foo.com/a~b#c~d?e~f'); + assert.equal(uri, 'http://foo.com'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://bar.io '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io'); + }); }); - it('should allow : character in URI path', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); + describe('should allow ~ character in URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); - const row = ' http://foo.com/colon:test '; + const row = ' http://foo.com/a~b#c~d?e~f '; - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; - assert.equal(uri, 'http://foo.com/colon:test'); + assert.equal(uri, 'http://foo.com/a~b#c~d?e~f'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://bar.io/a~b#c~d?e~f '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/a~b#c~d?e~f'); + }); }); - it('should not allow : character at the end of a URI path', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); + describe('should allow : character in URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); - const row = ' http://foo.com/colon:test: '; + const row = ' http://foo.com/colon:test '; - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; - assert.equal(uri, 'http://foo.com/colon:test'); + assert.equal(uri, 'http://foo.com/colon:test'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://bar.io/colon:test '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/colon:test'); + }); }); - it('should not allow " character at the end of a URI enclosed with ""', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); + describe('should not allow : character at the end of a URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); - const row = '"http://foo.com/"'; + const row = ' http://foo.com/colon:test: '; - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; - assert.equal(uri, 'http://foo.com/'); + assert.equal(uri, 'http://foo.com/colon:test'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://bar.io/colon:test: '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/colon:test'); + }); }); - it('should not allow \' character at the end of a URI enclosed with \'\'', () => { - const term = new MockTerminal(); - webLinks.webLinksInit(term); + describe('should not allow " character at the end of a URI enclosed with ""', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); - const row = '\'http://foo.com/\''; + const row = '"http://foo.com/"'; - const match = row.match(term.regex); - const uri = match[term.options.matchIndex]; + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; - assert.equal(uri, 'http://foo.com/'); + assert.equal(uri, 'http://foo.com/'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = '"http://bar.io/"'; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/'); + }); + }); + + describe('should not allow \' character at the end of a URI enclosed with \'\'', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = '\'http://foo.com/\''; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://foo.com/'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = '\'http://bar.io/\''; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/'); + }); }); }); diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts index f0d69cc5..9ebc8692 100644 --- a/src/addons/webLinks/webLinks.ts +++ b/src/addons/webLinks/webLinks.ts @@ -14,7 +14,8 @@ const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})'; const localHostClause = '(localhost)'; const portClause = '(:\\d{1,5})'; const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?'; -const pathClause = '(\\/[\\/\\w\\.\\-%~:]*)*([^:"\'\\s])'; +const pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:]*)*([^:"\'\\s])'; +const pathClause = '(' + pathCharacterSet + ')?'; const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?'; From 4269777f1c17a48a570f57c7242c702e20222ab0 Mon Sep 17 00:00:00 2001 From: roottool Date: Sat, 4 May 2019 02:54:38 +0900 Subject: [PATCH 19/97] Fix: Refactored the css code of keyframes blink --- src/renderer/dom/DomRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index ea3ac5d9..01d5c7dc 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -174,9 +174,9 @@ export class DomRenderer extends Disposable implements IRenderer { // Blink animation styles += `@keyframes blink {` + - ` 0 % { opacity: 1.0; }` + + ` 0% { opacity: 1.0; }` + ` 50% { opacity: 0.0; }` + - ` 100 % { opacity: 1.0; }` + + ` 100% { opacity: 1.0; }` + `}`; // Cursor styles += From 613f8779546ceeb1405dcd07e7a8125d16ab26ab Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 3 May 2019 21:07:21 -0700 Subject: [PATCH 20/97] Add a dev container This lets users using VS Code/Docker avoid installing node and the C++ dependencies necessary for running xterm.js. --- .devcontainer/Dockerfile | 22 ++++++++++++++++++++++ .devcontainer/devcontainer.json | 9 +++++++++ 2 files changed, 31 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..4d4e605b --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,22 @@ +FROM node:10 + +# Configure apt +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update \ + && apt-get -y install --no-install-recommends apt-utils 2>&1 + +# Verify git and process tools are installed +RUN apt-get install -y git procps + +# Install yarn +RUN apt-get install -y curl apt-transport-https lsb-release \ + && curl -sS https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \ + && echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ + && apt-get update \ + && apt-get -y install --no-install-recommends yarn + +# Clean up +RUN apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* +ENV DEBIAN_FRONTEND=dialog diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..b00a2693 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,9 @@ +{ + "name": "xterm.js", + "dockerFile": "Dockerfile", + "appPort": 3000, + "extensions": [ + "editorconfig.editorconfig", + "ms-vscode.vscode-typescript-tslint-plugin" + ] +} From ffcdba8d5b86231e5847fa913a0722da0b8f303b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 5 May 2019 21:48:35 +0200 Subject: [PATCH 21/97] simplify JoinedCellData --- src/renderer/BaseRenderLayer.ts | 5 ++++- src/renderer/CharacterJoinerRegistry.ts | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 00b38dcd..ace1f678 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -261,7 +261,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ protected drawChars(terminal: ITerminal, cell: ICellData, x: number, y: number): void { - // skip cache right away if we draw in RGB or have joined cells + // skip cache right away if we draw in RGB + // Note: to avoid bad runtime JoinedCellData will be skipped + // in the cache handler (atlasDidDraw == false) itself and + // fall through to uncached later down below if (cell.isFgRGB() || cell.isBgRGB() || cell instanceof JoinedCellData) { this._drawUncachedChars(terminal, cell, x, y); return; diff --git a/src/renderer/CharacterJoinerRegistry.ts b/src/renderer/CharacterJoinerRegistry.ts index 6a47ee22..7a3bb8e0 100644 --- a/src/renderer/CharacterJoinerRegistry.ts +++ b/src/renderer/CharacterJoinerRegistry.ts @@ -1,15 +1,15 @@ import { ITerminal, IBufferLine, ICellData, CharData } from '../Types'; import { ICharacterJoinerRegistry, ICharacterJoiner } from './Types'; -import { CellData, Content } from '../BufferLine'; +import { CellData, Content, AttributeData } from '../BufferLine'; import { WHITESPACE_CELL_CHAR } from '../Buffer'; -export class JoinedCellData extends CellData implements ICellData { - private _width: number = 0; - private _code: number = 0x1FFFFF; // highest allowed codepoint, meant as -1 - +export class JoinedCellData extends AttributeData implements ICellData { + private _width: number; + // .content carries no meaning for joined CellData, simply nullify it + // thus we have to overload all other .content accessors public content: number = 0; - public fg: number = 0; - public bg: number = 0; + public fg: number; + public bg: number; public combinedData: string = ''; constructor(firstCell: ICellData, chars: string, width: number) { @@ -34,7 +34,9 @@ export class JoinedCellData extends CellData implements ICellData { } public getCode(): number { - return this._code; + // code always gets the highest possible fake codepoint (read as -1) + // this is needed as code is used by caches as identifier + return 0x1FFFFF; } public setFromCharData(value: CharData): void { From a88469aac6edd162f6461112d19cecb6c8095c47 Mon Sep 17 00:00:00 2001 From: cubiclesoft Date: Tue, 7 May 2019 12:07:00 -0700 Subject: [PATCH 22/97] Added PHP App Server to Real-world uses section. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b2e9f02f..cbb2d697 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,8 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**Jumpserver**](https://github.com/jumpserver/luna): Jumpserver Luna project, Jumpserver is a bastion server project, Luna use xterm.js for web terminal emulation. - [**LxdMosaic**](https://github.com/turtle0x1/LxdMosaic): Uses xterm.js to give terminal access to containers through LXD - [**CodeInterview.io**](https://codeinterview.io): A coding interview platform in 25+ languages and many web frameworks. Uses xterm.js to provide shell access. -- [**Bastillion**](https://www.bastillion.io): Bastillion is an open-source web-based SSH console that centrally manages administrative access to systems. +- [**Bastillion**](https://www.bastillion.io): Bastillion is an open-source web-based SSH console that centrally manages administrative access to systems. +- [**PHP App Server**](https://github.com/cubiclesoft/php-app-server/): Create lightweight, installable almost-native applications for desktop OSes. ExecTerminal (nicely wraps the xterm.js Terminal), TerminalManager, and RunProcessSDK are self-contained, reusable ES5+ compliant Javascript components. [And much more...](https://github.com/xtermjs/xterm.js/network/dependents) From e831bdf1ef4636f5362fe8f3d941f905c46f84b6 Mon Sep 17 00:00:00 2001 From: Khaja Nizamuddin Date: Wed, 8 May 2019 21:06:22 +0530 Subject: [PATCH 23/97] Allow + char in URI path --- src/addons/webLinks/webLinks.test.ts | 26 ++++++++++++++++++++++++++ src/addons/webLinks/webLinks.ts | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/addons/webLinks/webLinks.test.ts b/src/addons/webLinks/webLinks.test.ts index 1423e010..da5569ab 100644 --- a/src/addons/webLinks/webLinks.test.ts +++ b/src/addons/webLinks/webLinks.test.ts @@ -183,4 +183,30 @@ describe('webLinks addon', () => { assert.equal(uri, 'http://bar.io/'); }); }); + + describe('should allow + character in URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = 'http://foo.com/subpath/+/id'; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://foo.com/subpath/+/id'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = 'http://bar.io/subpath/+/id'; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/subpath/+/id'); + }); + }); }); diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts index 9ebc8692..8a0fec09 100644 --- a/src/addons/webLinks/webLinks.ts +++ b/src/addons/webLinks/webLinks.ts @@ -14,7 +14,7 @@ const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})'; const localHostClause = '(localhost)'; const portClause = '(:\\d{1,5})'; const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?'; -const pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:]*)*([^:"\'\\s])'; +const pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:+]*)*([^:"\'\\s])'; const pathClause = '(' + pathCharacterSet + ')?'; const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; From 0d5cd9582fb5dd62d2ee6dbac714725410226c13 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 8 May 2019 18:03:06 -0700 Subject: [PATCH 24/97] Base the next beta version on the correct current beta Fixes #2044 --- bin/publish.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bin/publish.js b/bin/publish.js index d5e5047e..350ceceb 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -44,7 +44,14 @@ function getNextBetaVersion() { if (publishedVersions.length === 0) { return `${packageJson.version}-${tag}1`; } - const latestPublishedVersion = publishedVersions.sort((a, b) => b.localeCompare(a))[0]; + const latestPublishedVersion = publishedVersions.sort((a, b) => { + if (b.length > a.length) { + return true; + } else if (b.length < a.length) { + return false; + } + return b.localeCompare(a) + })[0]; const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10); return `${nextStableVersion}-${tag}${latestTagVersion + 1}`; } From 4b1c54d634bd2b6afc63a2626106ab9b58dd67aa Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 8 May 2019 20:53:24 -0700 Subject: [PATCH 25/97] Update terminal dimensions after line height changes Fixes #2048 --- demo/client.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/demo/client.ts b/demo/client.ts index c73d81dd..90a913d7 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -259,8 +259,11 @@ function initOptions(term: TerminalType): void { console.log('change', o, input.value); if (o === 'cols' || o === 'rows') { updateTerminalSize(); + } else if (o === 'lineHeight') { + term.setOption(o, parseFloat(input.value)); + updateTerminalSize(); } else { - term.setOption(o, o === 'lineHeight' ? parseFloat(input.value) : parseInt(input.value, 10)); + term.setOption(o, parseInt(input.value)); } }); }); From 71886167be39c4e43e4a0584eab3989196a5c271 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 8 May 2019 23:08:39 -0700 Subject: [PATCH 26/97] Replace prepublish On prepublishOnly we want to do a full build, on prepare we want to only build the ts projects so the .d.ts files are avilable. Fixes #2019 --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 539b96da..41d3e52a 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,8 @@ "mocha": "gulp test", "prebuild": "tsc -b ./src/tsconfig.all.json", "build": "gulp build", - "prepublish": "npm run build", + "prepare": "npm run prebuild", + "prepublishOnly": "npm run build", "coveralls": "nyc report --reporter=text-lcov | coveralls", "watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput" } From dc236aeb204d303c91b69b50bf49a61a82e1b6fb Mon Sep 17 00:00:00 2001 From: Jared Flatow Date: Thu, 9 May 2019 00:06:32 -0700 Subject: [PATCH 27/97] Send the unit separator for C-_, like some other keyboards --- src/core/input/Keyboard.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/input/Keyboard.ts b/src/core/input/Keyboard.ts index 5e4c4e61..36097f91 100644 --- a/src/core/input/Keyboard.ts +++ b/src/core/input/Keyboard.ts @@ -352,6 +352,10 @@ export function evaluateKeyboardEvent( } else if (ev.key && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.keyCode >= 48 && ev.key.length === 1) { // Include only keys that that result in a _single_ character; don't include num lock, volume up, etc. result.key = ev.key; + } else if (ev.key && ev.ctrlKey) { + if (ev.key === '_') { // ^_ + result.key = C0.US; + } } break; } From b959fc8d6a1faa2674607e25af455c17bc9df6af Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 May 2019 07:49:50 -0700 Subject: [PATCH 28/97] Update year in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cbb2d697..83170eff 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,6 @@ You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Co If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work. -Copyright (c) 2017-2018, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)
+Copyright (c) 2017-2019, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)
Copyright (c) 2014-2017, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)
Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) From 79e182d63f8dcbe5f98462f5811771ddce294029 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 May 2019 08:19:33 -0700 Subject: [PATCH 29/97] Actually fix beta CD Part of #2044 --- bin/publish.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/bin/publish.js b/bin/publish.js index 350ceceb..d55c9b97 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -45,12 +45,9 @@ function getNextBetaVersion() { return `${packageJson.version}-${tag}1`; } const latestPublishedVersion = publishedVersions.sort((a, b) => { - if (b.length > a.length) { - return true; - } else if (b.length < a.length) { - return false; - } - return b.localeCompare(a) + const aVersion = parseInt(a.substr(a.search(/[0-9]+$/))); + const bVersion = parseInt(b.substr(b.search(/[0-9]+$/))); + return aVersion > bVersion ? -1 : 1; })[0]; const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10); return `${nextStableVersion}-${tag}${latestTagVersion + 1}`; From e40f9249134ca3f5038fb95fd4eb5f0d036b1228 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 May 2019 09:37:07 -0700 Subject: [PATCH 30/97] v3.13.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d58a6a5d..afa6ca5d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xterm", "description": "Full xterm terminal, in your browser", - "version": "3.12.0", + "version": "3.13.0", "main": "lib/public/Terminal.js", "types": "typings/xterm.d.ts", "repository": "https://github.com/xtermjs/xterm.js", @@ -61,4 +61,4 @@ "coveralls": "nyc report --reporter=text-lcov | coveralls", "watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput" } -} \ No newline at end of file +} From 3abc090412bf2f2d6728d4606fdfff39c1977d78 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 May 2019 10:02:42 -0700 Subject: [PATCH 31/97] Add missing space to typings --- typings/xterm.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index b6d1e320..9f1fbe62 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -594,6 +594,7 @@ declare module 'xterm' { * @return An IDisposable you can call to remove this handler. */ addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; + /** * (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to * be matched and handled. From 6d55ab23fd38482008e873930b7a4ff32a8d7b70 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 May 2019 10:11:25 -0700 Subject: [PATCH 32/97] Fix beta CD for new major/minor beta versions --- bin/publish.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/publish.js b/bin/publish.js index d55c9b97..3b13d6a5 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -42,7 +42,7 @@ function getNextBetaVersion() { const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.${stableVersion[2]}`; const publishedVersions = getPublishedVersions(nextStableVersion, tag); if (publishedVersions.length === 0) { - return `${packageJson.version}-${tag}1`; + return `${nextStableVersion}-${tag}1`; } const latestPublishedVersion = publishedVersions.sort((a, b) => { const aVersion = parseInt(a.substr(a.search(/[0-9]+$/))); From f381cdfc331a15b51ec606fb7bd89815a2bbd5f4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 May 2019 21:57:33 -0700 Subject: [PATCH 33/97] Move a bunch of types to core --- src/Buffer.ts | 27 +----- src/BufferLine.test.ts | 6 +- src/BufferLine.ts | 26 +++++- src/BufferReflow.test.ts | 3 +- src/BufferReflow.ts | 2 +- src/BufferSet.ts | 3 +- src/CharWidth.test.ts | 3 +- src/InputHandler.test.ts | 2 +- src/InputHandler.ts | 4 +- src/Linkifier.test.ts | 3 +- src/SelectionManager.test.ts | 3 +- src/SelectionManager.ts | 3 +- src/Terminal.integration.ts | 3 +- src/Terminal.ts | 3 +- src/TestUtils.test.ts | 3 +- src/Types.ts | 82 +------------------ src/WindowsMode.ts | 2 +- src/core/Types.ts | 79 ++++++++++++++++++ src/handlers/AltClickHandler.ts | 3 +- src/renderer/BaseRenderLayer.ts | 6 +- src/renderer/CharacterJoinerRegistry.test.ts | 7 +- src/renderer/CharacterJoinerRegistry.ts | 11 ++- src/renderer/CursorRenderLayer.ts | 3 +- src/renderer/TextRenderLayer.ts | 6 +- .../dom/DomRendererRowFactory.test.ts | 7 +- src/renderer/dom/DomRendererRowFactory.ts | 6 +- 26 files changed, 161 insertions(+), 145 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index c1c08c85..db02fd0d 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -4,9 +4,10 @@ */ import { CircularList, IInsertEvent } from './common/CircularList'; -import { ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, ICellData, IAttributeData } from './Types'; +import { ITerminal, IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from './Types'; +import { IBufferLine, ICellData, IAttributeData } from './core/Types'; import { IMarker } from 'xterm'; -import { BufferLine, CellData, AttributeData } from './BufferLine'; +import { BufferLine, CellData, AttributeData, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './BufferLine'; import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from './BufferReflow'; import { DEFAULT_COLOR } from './renderer/atlas/Types'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; @@ -16,30 +17,8 @@ export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); export const DEFAULT_ATTR_DATA = new AttributeData(); -export const CHAR_DATA_ATTR_INDEX = 0; -export const CHAR_DATA_CHAR_INDEX = 1; -export const CHAR_DATA_WIDTH_INDEX = 2; -export const CHAR_DATA_CODE_INDEX = 3; export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 -/** - * Null cell - a real empty cell (containing nothing). - * Note that code should always be 0 for a null cell as - * several test condition of the buffer line rely on this. - */ -export const NULL_CELL_CHAR = ''; -export const NULL_CELL_WIDTH = 1; -export const NULL_CELL_CODE = 0; - -/** - * Whitespace cell. - * This is meant as a replacement for empty cells when needed - * during rendering lines to preserve correct aligment. - */ -export const WHITESPACE_CELL_CHAR = ' '; -export const WHITESPACE_CELL_WIDTH = 1; -export const WHITESPACE_CELL_CODE = 32; - /** * This class represents a terminal buffer (an internal state of the terminal), where the * following information is stored (in high-level): diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 5b029cb8..82e9610d 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -3,9 +3,9 @@ * @license MIT */ import * as chai from 'chai'; -import { BufferLine, CellData, Content } from './BufferLine'; -import { CharData, IBufferLine } from './Types'; -import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './Buffer'; +import { BufferLine, CellData, Content, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './BufferLine'; +import { CharData, IBufferLine } from './core/Types'; +import { DEFAULT_ATTR } from './Buffer'; class TestBufferLine extends BufferLine { diff --git a/src/BufferLine.ts b/src/BufferLine.ts index d2f7af40..92ed1de4 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -2,11 +2,33 @@ * Copyright (c) 2018 The xterm.js authors. All rights reserved. * @license MIT */ -import { CharData, IBufferLine, ICellData, IColorRGB, IAttributeData } from './Types'; -import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, WHITESPACE_CELL_CHAR, CHAR_DATA_ATTR_INDEX } from './Buffer'; +import { CharData, IBufferLine, ICellData, IColorRGB, IAttributeData } from './core/Types'; import { stringFromCodePoint } from './core/input/TextDecoder'; +export const CHAR_DATA_ATTR_INDEX = 0; +export const CHAR_DATA_CHAR_INDEX = 1; +export const CHAR_DATA_WIDTH_INDEX = 2; +export const CHAR_DATA_CODE_INDEX = 3; + +/** + * Null cell - a real empty cell (containing nothing). + * Note that code should always be 0 for a null cell as + * several test condition of the buffer line rely on this. + */ +export const NULL_CELL_CHAR = ''; +export const NULL_CELL_WIDTH = 1; +export const NULL_CELL_CODE = 0; + +/** + * Whitespace cell. + * This is meant as a replacement for empty cells when needed + * during rendering lines to preserve correct aligment. + */ +export const WHITESPACE_CELL_CHAR = ' '; +export const WHITESPACE_CELL_WIDTH = 1; +export const WHITESPACE_CELL_CODE = 32; + /** * buffer memory layout: * diff --git a/src/BufferReflow.test.ts b/src/BufferReflow.test.ts index 9c978dc0..9a2f3906 100644 --- a/src/BufferReflow.test.ts +++ b/src/BufferReflow.test.ts @@ -3,9 +3,8 @@ * @license MIT */ import { assert } from 'chai'; -import { BufferLine } from './BufferLine'; +import { BufferLine, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './BufferLine'; import { reflowSmallerGetNewLineLengths } from './BufferReflow'; -import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer'; describe('BufferReflow', () => { describe('reflowSmallerGetNewLineLengths', () => { diff --git a/src/BufferReflow.ts b/src/BufferReflow.ts index 40e16c74..7c53de7a 100644 --- a/src/BufferReflow.ts +++ b/src/BufferReflow.ts @@ -5,7 +5,7 @@ import { BufferLine } from './BufferLine'; import { CircularList } from './common/CircularList'; -import { IBufferLine, ICellData } from './Types'; +import { IBufferLine, ICellData } from './core/Types'; export interface INewLayoutResult { layout: number[]; diff --git a/src/BufferSet.ts b/src/BufferSet.ts index ba885a0e..f22b92dc 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { ITerminal, IBufferSet, IAttributeData, IBuffer } from './Types'; +import { ITerminal, IBufferSet, IBuffer } from './Types'; +import { IAttributeData } from './core/Types'; import { Buffer } from './Buffer'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; diff --git a/src/CharWidth.test.ts b/src/CharWidth.test.ts index ff6f17ed..0821d864 100644 --- a/src/CharWidth.test.ts +++ b/src/CharWidth.test.ts @@ -7,8 +7,7 @@ import { TestTerminal } from './TestUtils.test'; import { assert } from 'chai'; import { getStringCellWidth, wcwidth } from './CharWidth'; import { IBuffer } from './Types'; -import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer'; -import { CellData } from './BufferLine'; +import { CellData, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './BufferLine'; describe('getStringCellWidth', function(): void { diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index dcd4d1b4..3089aa0a 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -8,7 +8,7 @@ import { InputHandler } from './InputHandler'; import { MockInputHandlingTerminal, TestTerminal } from './TestUtils.test'; import { DEFAULT_ATTR_DATA } from './Buffer'; import { Terminal } from './Terminal'; -import { IBufferLine } from './Types'; +import { IBufferLine } from './core/Types'; import { CellData, Attributes, AttributeData } from './BufferLine'; describe('InputHandler', () => { diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 66d16c04..dc2e5eb2 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,14 +7,14 @@ import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IInputHandlingTerminal } from './Types'; import { C0, C1 } from './common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets'; -import { NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR_DATA } from './Buffer'; +import { DEFAULT_ATTR_DATA } from './Buffer'; import { wcwidth } from './CharWidth'; import { EscapeSequenceParser } from './EscapeSequenceParser'; import { IDisposable } from 'xterm'; import { Disposable } from './common/Lifecycle'; import { concat } from './common/TypedArrayUtils'; import { StringToUtf32, stringFromCodePoint, utf32ToString } from './core/input/TextDecoder'; -import { CellData, Attributes, FgFlags, BgFlags, AttributeData } from './BufferLine'; +import { CellData, Attributes, FgFlags, BgFlags, AttributeData, NULL_CELL_WIDTH, NULL_CELL_CODE } from './BufferLine'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; /** diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index 8f734bac..d22244c9 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -4,7 +4,8 @@ */ import { assert } from 'chai'; -import { IMouseZoneManager, IMouseZone, ILinkMatcher, ITerminal, IBufferLine } from './Types'; +import { IMouseZoneManager, IMouseZone, ILinkMatcher, ITerminal } from './Types'; +import { IBufferLine } from './core/Types'; import { Linkifier } from './Linkifier'; import { MockBuffer, MockTerminal, TestTerminal } from './TestUtils.test'; import { CircularList } from './common/CircularList'; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 9a2ac405..07bbe8be 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -8,7 +8,8 @@ import { CharMeasure } from './CharMeasure'; import { SelectionManager, SelectionMode } from './SelectionManager'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; -import { ITerminal, IBuffer, IBufferLine } from './Types'; +import { ITerminal, IBuffer } from './Types'; +import { IBufferLine } from './core/Types'; import { MockTerminal } from './TestUtils.test'; import { BufferLine, CellData } from './BufferLine'; diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index dcd60068..2d651348 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { ITerminal, ISelectionManager, IBuffer, IBufferLine, ISelectionRedrawRequestEvent } from './Types'; +import { ITerminal, ISelectionManager, IBuffer, ISelectionRedrawRequestEvent } from './Types'; +import { IBufferLine } from './core/Types'; import { MouseHelper } from './MouseHelper'; import * as Browser from './common/Platform'; import { CharMeasure } from './CharMeasure'; diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index 10043006..b03627df 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -13,9 +13,8 @@ import * as path from 'path'; import * as pty from 'node-pty'; import { assert } from 'chai'; import { Terminal } from './Terminal'; -import { WHITESPACE_CELL_CHAR } from './Buffer'; import { IViewport } from './Types'; -import { CellData } from './BufferLine'; +import { CellData, WHITESPACE_CELL_CHAR } from './BufferLine'; class TestTerminal extends Terminal { innerWrite(): void { this._innerWrite(); } diff --git a/src/Terminal.ts b/src/Terminal.ts index 3fe5b590..3653efc7 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -21,7 +21,8 @@ * http://linux.die.net/man/7/urxvt */ -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharacterJoinerHandler, IBufferLine, IAttributeData, IMouseZoneManager } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharacterJoinerHandler, IMouseZoneManager } from './Types'; +import { IBufferLine, IAttributeData } from './core/Types'; import { IRenderer } from './renderer/Types'; import { BufferSet } from './BufferSet'; import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR_DATA } from './Buffer'; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 92018d1f..8170681d 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -4,7 +4,8 @@ */ import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from './renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator, ICellData, IAttributeData } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferStringIterator } from './Types'; +import { IBufferLine, ICellData, IAttributeData } from './core/Types'; import { ICircularList, XtermListener } from './common/Types'; import { Buffer } from './Buffer'; import * as Browser from './common/Platform'; diff --git a/src/Types.ts b/src/Types.ts index e603e92b..56feb71f 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -5,13 +5,12 @@ import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable } from 'xterm'; import { IColorSet, IRenderer } from './renderer/Types'; -import { ICharset } from './core/Types'; +import { ICharset, IAttributeData, ICellData, IBufferLine, CharData } from './core/Types'; import { ICircularList } from './common/Types'; import { IEvent } from './common/EventEmitter2'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; -export type CharData = [number, string, number, number]; export type LineData = CharData[]; export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void; @@ -525,85 +524,6 @@ export interface IEscapeSequenceParser extends IDisposable { clearErrorHandler(): void; } -/** RGB color type */ -export type IColorRGB = [number, number, number]; - -/** Attribute data */ -export interface IAttributeData { - fg: number; - bg: number; - - clone(): IAttributeData; - - // flags - isInverse(): number; - isBold(): number; - isUnderline(): number; - isBlink(): number; - isInvisible(): number; - isItalic(): number; - isDim(): number; - - // color modes - getFgColorMode(): number; - getBgColorMode(): number; - isFgRGB(): boolean; - isBgRGB(): boolean; - isFgPalette(): boolean; - isBgPalette(): boolean; - isFgDefault(): boolean; - isBgDefault(): boolean; - - // colors - getFgColor(): number; - getBgColor(): number; -} - -/** Cell data */ -export interface ICellData extends IAttributeData { - content: number; - combinedData: string; - isCombined(): number; - getWidth(): number; - getChars(): string; - getCode(): number; - setFromCharData(value: CharData): void; - getAsCharData(): CharData; -} - -/** - * Interface for a line in the terminal buffer. - */ -export interface IBufferLine { - length: number; - isWrapped: boolean; - get(index: number): CharData; - set(index: number, value: CharData): void; - loadCell(index: number, cell: ICellData): ICellData; - setCell(index: number, cell: ICellData): void; - setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void; - addCodepointToCell(index: number, codePoint: number): void; - insertCells(pos: number, n: number, ch: ICellData): void; - deleteCells(pos: number, n: number, fill: ICellData): void; - replaceCells(start: number, end: number, fill: ICellData): void; - resize(cols: number, fill: ICellData): void; - fill(fillCellData: ICellData): void; - copyFrom(line: IBufferLine): void; - clone(): IBufferLine; - getTrimmedLength(): number; - translateToString(trimRight?: boolean, startCol?: number, endCol?: number): string; - - /* direct access to cell attrs */ - getWidth(index: number): number; - hasWidth(index: number): number; - getFg(index: number): number; - getBg(index: number): number; - hasContent(index: number): number; - getCodePoint(index: number): number; - isCombined(index: number): number; - getString(index: number): string; -} - export interface IMouseZoneManager extends IDisposable { add(zone: IMouseZone): void; clearAll(start?: number, end?: number): void; diff --git a/src/WindowsMode.ts b/src/WindowsMode.ts index ac1d193e..1e58c50c 100644 --- a/src/WindowsMode.ts +++ b/src/WindowsMode.ts @@ -5,7 +5,7 @@ import { IDisposable } from 'xterm'; import { ITerminal } from './Types'; -import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from './Buffer'; +import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from './BufferLine'; export function applyWindowsMode(terminal: ITerminal): IDisposable { // Winpty does not support wraparound mode which means that lines will never diff --git a/src/core/Types.ts b/src/core/Types.ts index 4001e448..ae9274b8 100644 --- a/src/core/Types.ts +++ b/src/core/Types.ts @@ -19,3 +19,82 @@ export interface IKeyboardResult { export interface ICharset { [key: string]: string; } + +export type CharData = [number, string, number, number]; +export type IColorRGB = [number, number, number]; + +/** Attribute data */ +export interface IAttributeData { + fg: number; + bg: number; + + clone(): IAttributeData; + + // flags + isInverse(): number; + isBold(): number; + isUnderline(): number; + isBlink(): number; + isInvisible(): number; + isItalic(): number; + isDim(): number; + + // color modes + getFgColorMode(): number; + getBgColorMode(): number; + isFgRGB(): boolean; + isBgRGB(): boolean; + isFgPalette(): boolean; + isBgPalette(): boolean; + isFgDefault(): boolean; + isBgDefault(): boolean; + + // colors + getFgColor(): number; + getBgColor(): number; +} + +/** Cell data */ +export interface ICellData extends IAttributeData { + content: number; + combinedData: string; + isCombined(): number; + getWidth(): number; + getChars(): string; + getCode(): number; + setFromCharData(value: CharData): void; + getAsCharData(): CharData; +} + +/** + * Interface for a line in the terminal buffer. + */ +export interface IBufferLine { + length: number; + isWrapped: boolean; + get(index: number): CharData; + set(index: number, value: CharData): void; + loadCell(index: number, cell: ICellData): ICellData; + setCell(index: number, cell: ICellData): void; + setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void; + addCodepointToCell(index: number, codePoint: number): void; + insertCells(pos: number, n: number, ch: ICellData): void; + deleteCells(pos: number, n: number, fill: ICellData): void; + replaceCells(start: number, end: number, fill: ICellData): void; + resize(cols: number, fill: ICellData): void; + fill(fillCellData: ICellData): void; + copyFrom(line: IBufferLine): void; + clone(): IBufferLine; + getTrimmedLength(): number; + translateToString(trimRight?: boolean, startCol?: number, endCol?: number): string; + + /* direct access to cell attrs */ + getWidth(index: number): number; + hasWidth(index: number): number; + getFg(index: number): number; + getBg(index: number): number; + hasContent(index: number): number; + getCodePoint(index: number): number; + isCombined(index: number): number; + getString(index: number): string; +} diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 9286421e..5cf60fa6 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { ITerminal, IBufferLine } from '../Types'; +import { ITerminal } from '../Types'; +import { IBufferLine } from '../core/Types'; import { ICircularList } from '../common/Types'; import { C0 } from '../common/data/EscapeSequences'; diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index ace1f678..8b1e27c5 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -4,12 +4,12 @@ */ import { IRenderLayer, IColorSet, IRenderDimensions } from './Types'; -import { ITerminal, ICellData } from '../Types'; +import { ITerminal } from '../Types'; +import { ICellData } from '../core/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier, DEFAULT_COLOR } from './atlas/Types'; import BaseCharAtlas from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; -import { CellData, AttributeData } from '../BufferLine'; -import { WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../Buffer'; +import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../BufferLine'; import { JoinedCellData } from './CharacterJoinerRegistry'; export abstract class BaseRenderLayer implements IRenderLayer { diff --git a/src/renderer/CharacterJoinerRegistry.test.ts b/src/renderer/CharacterJoinerRegistry.test.ts index 2ccfd197..590d6a5f 100644 --- a/src/renderer/CharacterJoinerRegistry.test.ts +++ b/src/renderer/CharacterJoinerRegistry.test.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { assert } from 'chai'; import { MockTerminal, MockBuffer } from '../TestUtils.test'; @@ -6,7 +11,7 @@ import { CircularList } from '../common/CircularList'; import { ICharacterJoinerRegistry } from './Types'; import { CharacterJoinerRegistry } from './CharacterJoinerRegistry'; import { BufferLine, CellData } from '../BufferLine'; -import { IBufferLine } from '../Types'; +import { IBufferLine } from '../core/Types'; describe('CharacterJoinerRegistry', () => { let registry: ICharacterJoinerRegistry; diff --git a/src/renderer/CharacterJoinerRegistry.ts b/src/renderer/CharacterJoinerRegistry.ts index 7a3bb8e0..33cd8936 100644 --- a/src/renderer/CharacterJoinerRegistry.ts +++ b/src/renderer/CharacterJoinerRegistry.ts @@ -1,7 +1,12 @@ -import { ITerminal, IBufferLine, ICellData, CharData } from '../Types'; +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ITerminal } from '../Types'; +import { IBufferLine, ICellData, CharData } from '../core/Types'; import { ICharacterJoinerRegistry, ICharacterJoiner } from './Types'; -import { CellData, Content, AttributeData } from '../BufferLine'; -import { WHITESPACE_CELL_CHAR } from '../Buffer'; +import { CellData, Content, AttributeData, WHITESPACE_CELL_CHAR } from '../BufferLine'; export class JoinedCellData extends AttributeData implements ICellData { private _width: number; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index c3b751fa..72aaa447 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -5,7 +5,8 @@ import { IColorSet, IRenderDimensions } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { ITerminal, ICellData } from '../Types'; +import { ITerminal } from '../Types'; +import { ICellData } from '../core/Types'; import { CellData } from '../BufferLine'; interface ICursorState { diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index d54008b8..90c7e267 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -3,12 +3,12 @@ * @license MIT */ -import { NULL_CELL_CODE } from '../Buffer'; import { IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; -import { CharData, ITerminal, ICellData } from '../Types'; +import { ITerminal } from '../Types'; +import { CharData, ICellData } from '../core/Types'; import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { CellData, AttributeData, Content } from '../BufferLine'; +import { CellData, AttributeData, Content, NULL_CELL_CODE } from '../BufferLine'; import { JoinedCellData } from './CharacterJoinerRegistry'; /** diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index 076f5d6a..c795c5ae 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -6,9 +6,10 @@ import jsdom = require('jsdom'); import { assert } from 'chai'; import { DomRendererRowFactory } from './DomRendererRowFactory'; -import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR_DATA } from '../../Buffer'; -import { BufferLine, CellData, FgFlags, BgFlags, Attributes } from '../../BufferLine'; -import { IBufferLine, ITerminalOptions } from '../../Types'; +import { DEFAULT_ATTR, DEFAULT_ATTR_DATA } from '../../Buffer'; +import { BufferLine, CellData, FgFlags, BgFlags, Attributes, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '../../BufferLine'; +import { ITerminalOptions } from '../../Types'; +import { IBufferLine } from '../../core/Types'; describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 60e2d509..481d096e 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -3,10 +3,10 @@ * @license MIT */ -import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../Buffer'; -import { IBufferLine, ITerminalOptions } from '../../Types'; +import { ITerminalOptions } from '../../Types'; +import { IBufferLine } from '../../core/Types'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; -import { CellData, AttributeData } from '../../BufferLine'; +import { CellData, AttributeData, NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../BufferLine'; export const BOLD_CLASS = 'xterm-bold'; export const DIM_CLASS = 'xterm-dim'; From 7b7fc53157c807f1389f8cfed6570c27966dfde4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 May 2019 22:10:07 -0700 Subject: [PATCH 34/97] Move more BufferLine dependencies into core --- src/Buffer.test.ts | 4 ++-- src/Buffer.ts | 7 +------ src/BufferLine.test.ts | 3 +-- src/BufferLine.ts | 4 ++++ src/InputHandler.test.ts | 3 +-- src/InputHandler.ts | 3 +-- src/Terminal.test.ts | 3 +-- src/Terminal.ts | 7 +++---- src/common/Types.ts | 2 ++ src/renderer/BaseRenderLayer.ts | 3 ++- src/renderer/atlas/CharAtlasUtils.ts | 3 ++- src/renderer/atlas/StaticCharAtlas.ts | 3 ++- src/renderer/atlas/Types.ts | 1 - src/renderer/dom/DomRendererRowFactory.test.ts | 3 +-- 14 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index d866fd56..618a9795 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -5,10 +5,10 @@ import { assert, expect } from 'chai'; import { ITerminal } from './Types'; -import { Buffer, DEFAULT_ATTR_DATA } from './Buffer'; +import { Buffer } from './Buffer'; import { CircularList } from './common/CircularList'; import { MockTerminal, TestTerminal } from './TestUtils.test'; -import { BufferLine, CellData } from './BufferLine'; +import { BufferLine, CellData, DEFAULT_ATTR_DATA } from './BufferLine'; const INIT_COLS = 80; const INIT_ROWS = 24; diff --git a/src/Buffer.ts b/src/Buffer.ts index db02fd0d..7e986a6c 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -7,16 +7,11 @@ import { CircularList, IInsertEvent } from './common/CircularList'; import { ITerminal, IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from './Types'; import { IBufferLine, ICellData, IAttributeData } from './core/Types'; import { IMarker } from 'xterm'; -import { BufferLine, CellData, AttributeData, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './BufferLine'; +import { BufferLine, CellData, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, DEFAULT_ATTR_DATA } from './BufferLine'; import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from './BufferReflow'; -import { DEFAULT_COLOR } from './renderer/atlas/Types'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; import { Disposable } from '../lib/common/Lifecycle'; -export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); - -export const DEFAULT_ATTR_DATA = new AttributeData(); - export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 /** diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 82e9610d..b9e40491 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -3,9 +3,8 @@ * @license MIT */ import * as chai from 'chai'; -import { BufferLine, CellData, Content, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './BufferLine'; +import { BufferLine, CellData, Content, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './BufferLine'; import { CharData, IBufferLine } from './core/Types'; -import { DEFAULT_ATTR } from './Buffer'; class TestBufferLine extends BufferLine { diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 92ed1de4..39b6116d 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -4,7 +4,9 @@ */ import { CharData, IBufferLine, ICellData, IColorRGB, IAttributeData } from './core/Types'; import { stringFromCodePoint } from './core/input/TextDecoder'; +import { DEFAULT_COLOR } from './common/Types'; +export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); export const CHAR_DATA_ATTR_INDEX = 0; export const CHAR_DATA_CHAR_INDEX = 1; @@ -215,6 +217,8 @@ export class AttributeData implements IAttributeData { } } +export const DEFAULT_ATTR_DATA = new AttributeData(); + /** * CellData - represents a single Cell in the terminal buffer. */ diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 3089aa0a..c0286779 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -6,10 +6,9 @@ import { assert, expect } from 'chai'; import { InputHandler } from './InputHandler'; import { MockInputHandlingTerminal, TestTerminal } from './TestUtils.test'; -import { DEFAULT_ATTR_DATA } from './Buffer'; import { Terminal } from './Terminal'; import { IBufferLine } from './core/Types'; -import { CellData, Attributes, AttributeData } from './BufferLine'; +import { CellData, Attributes, AttributeData, DEFAULT_ATTR_DATA } from './BufferLine'; describe('InputHandler', () => { describe('save and restore cursor', () => { diff --git a/src/InputHandler.ts b/src/InputHandler.ts index dc2e5eb2..ebdc9d0d 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,14 +7,13 @@ import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IInputHandlingTerminal } from './Types'; import { C0, C1 } from './common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets'; -import { DEFAULT_ATTR_DATA } from './Buffer'; import { wcwidth } from './CharWidth'; import { EscapeSequenceParser } from './EscapeSequenceParser'; import { IDisposable } from 'xterm'; import { Disposable } from './common/Lifecycle'; import { concat } from './common/TypedArrayUtils'; import { StringToUtf32, stringFromCodePoint, utf32ToString } from './core/input/TextDecoder'; -import { CellData, Attributes, FgFlags, BgFlags, AttributeData, NULL_CELL_WIDTH, NULL_CELL_CODE } from './BufferLine'; +import { CellData, Attributes, FgFlags, BgFlags, AttributeData, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR_DATA } from './BufferLine'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; /** diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 69fbb8e2..06a02d68 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -6,8 +6,7 @@ import { assert, expect } from 'chai'; import { Terminal } from './Terminal'; import { MockViewport, MockCompositionHelper, MockRenderer } from './TestUtils.test'; -import { DEFAULT_ATTR_DATA } from './Buffer'; -import { CellData } from './BufferLine'; +import { CellData, DEFAULT_ATTR_DATA } from './BufferLine'; const INIT_COLS = 80; const INIT_ROWS = 24; diff --git a/src/Terminal.ts b/src/Terminal.ts index 3653efc7..0ff21b88 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -22,10 +22,9 @@ */ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharacterJoinerHandler, IMouseZoneManager } from './Types'; -import { IBufferLine, IAttributeData } from './core/Types'; import { IRenderer } from './renderer/Types'; import { BufferSet } from './BufferSet'; -import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR_DATA } from './Buffer'; +import { Buffer, MAX_BUFFER_SIZE } from './Buffer'; import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from './common/EventEmitter'; import { Viewport } from './Viewport'; @@ -49,10 +48,10 @@ import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; -import { KeyboardResultType, ICharset } from './core/Types'; +import { KeyboardResultType, ICharset, IBufferLine, IAttributeData } from './core/Types'; import { clone } from './common/Clone'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; -import { Attributes } from './BufferLine'; +import { Attributes, DEFAULT_ATTR_DATA } from './BufferLine'; import { applyWindowsMode } from './WindowsMode'; // Let it work inside Node.js for automated testing purposes. diff --git a/src/common/Types.ts b/src/common/Types.ts index b2111bfc..717de359 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -6,6 +6,8 @@ import { IEvent, EventEmitter2 } from './EventEmitter2'; import { IDeleteEvent, IInsertEvent } from './CircularList'; +export const DEFAULT_COLOR = 256; + export interface IDisposable { dispose(): void; } diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 8b1e27c5..63846155 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -6,7 +6,8 @@ import { IRenderLayer, IColorSet, IRenderDimensions } from './Types'; import { ITerminal } from '../Types'; import { ICellData } from '../core/Types'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier, DEFAULT_COLOR } from './atlas/Types'; +import { DEFAULT_COLOR } from '../common/Types'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/Types'; import BaseCharAtlas from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../BufferLine'; diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/renderer/atlas/CharAtlasUtils.ts index 5b1add39..c2eb2e1e 100644 --- a/src/renderer/atlas/CharAtlasUtils.ts +++ b/src/renderer/atlas/CharAtlasUtils.ts @@ -5,7 +5,8 @@ import { ITerminal } from '../../Types'; import { IColorSet } from '../Types'; -import { DEFAULT_COLOR, ICharAtlasConfig } from './Types'; +import { ICharAtlasConfig } from './Types'; +import { DEFAULT_COLOR } from '../../common/Types'; export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter diff --git a/src/renderer/atlas/StaticCharAtlas.ts b/src/renderer/atlas/StaticCharAtlas.ts index b54c833e..66beb363 100644 --- a/src/renderer/atlas/StaticCharAtlas.ts +++ b/src/renderer/atlas/StaticCharAtlas.ts @@ -3,10 +3,11 @@ * @license MIT */ -import { DIM_OPACITY, IGlyphIdentifier, DEFAULT_COLOR, ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; +import { DIM_OPACITY, IGlyphIdentifier, ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; import { generateStaticCharAtlasTexture } from './CharAtlasGenerator'; import BaseCharAtlas from './BaseCharAtlas'; import { is256Color } from './CharAtlasUtils'; +import { DEFAULT_COLOR } from '../../common/Types'; export default class StaticCharAtlas extends BaseCharAtlas { private _texture: HTMLCanvasElement | ImageBitmap; diff --git a/src/renderer/atlas/Types.ts b/src/renderer/atlas/Types.ts index 38923b2f..2459c72f 100644 --- a/src/renderer/atlas/Types.ts +++ b/src/renderer/atlas/Types.ts @@ -6,7 +6,6 @@ import { FontWeight } from 'xterm'; import { IColorSet } from '../Types'; -export const DEFAULT_COLOR = 256; export const INVERTED_DEFAULT_COLOR = 257; export const DIM_OPACITY = 0.5; diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index c795c5ae..f009407d 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -6,8 +6,7 @@ import jsdom = require('jsdom'); import { assert } from 'chai'; import { DomRendererRowFactory } from './DomRendererRowFactory'; -import { DEFAULT_ATTR, DEFAULT_ATTR_DATA } from '../../Buffer'; -import { BufferLine, CellData, FgFlags, BgFlags, Attributes, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '../../BufferLine'; +import { BufferLine, CellData, FgFlags, BgFlags, Attributes, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, DEFAULT_ATTR_DATA } from '../../BufferLine'; import { ITerminalOptions } from '../../Types'; import { IBufferLine } from '../../core/Types'; From 228ec7b818dc81bca10dce2e7aacfa5e4e078059 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 May 2019 22:15:32 -0700 Subject: [PATCH 35/97] Move BufferLine into core --- src/Buffer.test.ts | 2 +- src/Buffer.ts | 2 +- src/BufferLine.test.ts | 3 +-- src/BufferReflow.test.ts | 2 +- src/BufferReflow.ts | 2 +- src/CharWidth.test.ts | 2 +- src/InputHandler.test.ts | 2 +- src/InputHandler.ts | 2 +- src/Linkifier.test.ts | 2 +- src/SelectionManager.test.ts | 2 +- src/SelectionManager.ts | 2 +- src/Terminal.integration.ts | 2 +- src/Terminal.test.ts | 2 +- src/Terminal.ts | 2 +- src/TestUtils.test.ts | 2 +- src/WindowsMode.ts | 2 +- src/{ => core/buffer}/BufferLine.ts | 20 +++++++++---------- src/renderer/BaseRenderLayer.ts | 2 +- src/renderer/CharacterJoinerRegistry.test.ts | 2 +- src/renderer/CharacterJoinerRegistry.ts | 2 +- src/renderer/CursorRenderLayer.ts | 2 +- src/renderer/TextRenderLayer.ts | 2 +- .../dom/DomRendererRowFactory.test.ts | 2 +- src/renderer/dom/DomRendererRowFactory.ts | 2 +- 24 files changed, 32 insertions(+), 35 deletions(-) rename src/{ => core/buffer}/BufferLine.ts (97%) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 618a9795..087d0ae5 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -8,7 +8,7 @@ import { ITerminal } from './Types'; import { Buffer } from './Buffer'; import { CircularList } from './common/CircularList'; import { MockTerminal, TestTerminal } from './TestUtils.test'; -import { BufferLine, CellData, DEFAULT_ATTR_DATA } from './BufferLine'; +import { BufferLine, CellData, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine'; const INIT_COLS = 80; const INIT_ROWS = 24; diff --git a/src/Buffer.ts b/src/Buffer.ts index 7e986a6c..14bf53aa 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -7,7 +7,7 @@ import { CircularList, IInsertEvent } from './common/CircularList'; import { ITerminal, IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from './Types'; import { IBufferLine, ICellData, IAttributeData } from './core/Types'; import { IMarker } from 'xterm'; -import { BufferLine, CellData, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, DEFAULT_ATTR_DATA } from './BufferLine'; +import { BufferLine, CellData, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine'; import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from './BufferReflow'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; import { Disposable } from '../lib/common/Lifecycle'; diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index b9e40491..87a4e631 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -3,10 +3,9 @@ * @license MIT */ import * as chai from 'chai'; -import { BufferLine, CellData, Content, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './BufferLine'; +import { BufferLine, CellData, Content, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './core/buffer/BufferLine'; import { CharData, IBufferLine } from './core/Types'; - class TestBufferLine extends BufferLine { public get combined(): {[index: number]: string} { return this._combined; diff --git a/src/BufferReflow.test.ts b/src/BufferReflow.test.ts index 9a2f3906..91454e2a 100644 --- a/src/BufferReflow.test.ts +++ b/src/BufferReflow.test.ts @@ -3,7 +3,7 @@ * @license MIT */ import { assert } from 'chai'; -import { BufferLine, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './BufferLine'; +import { BufferLine, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './core/buffer/BufferLine'; import { reflowSmallerGetNewLineLengths } from './BufferReflow'; describe('BufferReflow', () => { diff --git a/src/BufferReflow.ts b/src/BufferReflow.ts index 7c53de7a..c1068967 100644 --- a/src/BufferReflow.ts +++ b/src/BufferReflow.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { BufferLine } from './BufferLine'; +import { BufferLine } from './core/buffer/BufferLine'; import { CircularList } from './common/CircularList'; import { IBufferLine, ICellData } from './core/Types'; diff --git a/src/CharWidth.test.ts b/src/CharWidth.test.ts index 0821d864..17822fa2 100644 --- a/src/CharWidth.test.ts +++ b/src/CharWidth.test.ts @@ -7,7 +7,7 @@ import { TestTerminal } from './TestUtils.test'; import { assert } from 'chai'; import { getStringCellWidth, wcwidth } from './CharWidth'; import { IBuffer } from './Types'; -import { CellData, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './BufferLine'; +import { CellData, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './core/buffer/BufferLine'; describe('getStringCellWidth', function(): void { diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index c0286779..f2e8d93e 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -8,7 +8,7 @@ import { InputHandler } from './InputHandler'; import { MockInputHandlingTerminal, TestTerminal } from './TestUtils.test'; import { Terminal } from './Terminal'; import { IBufferLine } from './core/Types'; -import { CellData, Attributes, AttributeData, DEFAULT_ATTR_DATA } from './BufferLine'; +import { CellData, Attributes, AttributeData, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine'; describe('InputHandler', () => { describe('save and restore cursor', () => { diff --git a/src/InputHandler.ts b/src/InputHandler.ts index ebdc9d0d..b5b98dd9 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -13,7 +13,7 @@ import { IDisposable } from 'xterm'; import { Disposable } from './common/Lifecycle'; import { concat } from './common/TypedArrayUtils'; import { StringToUtf32, stringFromCodePoint, utf32ToString } from './core/input/TextDecoder'; -import { CellData, Attributes, FgFlags, BgFlags, AttributeData, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR_DATA } from './BufferLine'; +import { CellData, Attributes, FgFlags, BgFlags, AttributeData, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; /** diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index d22244c9..6a2f0ee9 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -9,7 +9,7 @@ import { IBufferLine } from './core/Types'; import { Linkifier } from './Linkifier'; import { MockBuffer, MockTerminal, TestTerminal } from './TestUtils.test'; import { CircularList } from './common/CircularList'; -import { BufferLine, CellData } from './BufferLine'; +import { BufferLine, CellData } from './core/buffer/BufferLine'; class TestLinkifier extends Linkifier { constructor(terminal: ITerminal) { diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 07bbe8be..24ba0100 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -11,7 +11,7 @@ import { BufferSet } from './BufferSet'; import { ITerminal, IBuffer } from './Types'; import { IBufferLine } from './core/Types'; import { MockTerminal } from './TestUtils.test'; -import { BufferLine, CellData } from './BufferLine'; +import { BufferLine, CellData } from './core/buffer/BufferLine'; class TestMockTerminal extends MockTerminal { emit(event: string, data: any): void {} diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 2d651348..dabcc9a4 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -10,7 +10,7 @@ import * as Browser from './common/Platform'; import { CharMeasure } from './CharMeasure'; import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; -import { CellData } from './BufferLine'; +import { CellData } from './core/buffer/BufferLine'; import { IDisposable } from 'xterm'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index b03627df..5e12d2a3 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -14,7 +14,7 @@ import * as pty from 'node-pty'; import { assert } from 'chai'; import { Terminal } from './Terminal'; import { IViewport } from './Types'; -import { CellData, WHITESPACE_CELL_CHAR } from './BufferLine'; +import { CellData, WHITESPACE_CELL_CHAR } from './core/buffer/BufferLine'; class TestTerminal extends Terminal { innerWrite(): void { this._innerWrite(); } diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 06a02d68..9b5fa9c8 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -6,7 +6,7 @@ import { assert, expect } from 'chai'; import { Terminal } from './Terminal'; import { MockViewport, MockCompositionHelper, MockRenderer } from './TestUtils.test'; -import { CellData, DEFAULT_ATTR_DATA } from './BufferLine'; +import { CellData, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine'; const INIT_COLS = 80; const INIT_ROWS = 24; diff --git a/src/Terminal.ts b/src/Terminal.ts index 0ff21b88..6da60458 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -51,7 +51,7 @@ import { evaluateKeyboardEvent } from './core/input/Keyboard'; import { KeyboardResultType, ICharset, IBufferLine, IAttributeData } from './core/Types'; import { clone } from './common/Clone'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; -import { Attributes, DEFAULT_ATTR_DATA } from './BufferLine'; +import { Attributes, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine'; import { applyWindowsMode } from './WindowsMode'; // Let it work inside Node.js for automated testing purposes. diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 8170681d..1d5008c8 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -11,7 +11,7 @@ import { Buffer } from './Buffer'; import * as Browser from './common/Platform'; import { ITheme, IDisposable, IMarker, IEvent } from 'xterm'; import { Terminal } from './Terminal'; -import { AttributeData } from './BufferLine'; +import { AttributeData } from './core/buffer/BufferLine'; export class TestTerminal extends Terminal { writeSync(data: string): void { diff --git a/src/WindowsMode.ts b/src/WindowsMode.ts index 1e58c50c..d7b4bcae 100644 --- a/src/WindowsMode.ts +++ b/src/WindowsMode.ts @@ -5,7 +5,7 @@ import { IDisposable } from 'xterm'; import { ITerminal } from './Types'; -import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from './BufferLine'; +import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from './core/buffer/BufferLine'; export function applyWindowsMode(terminal: ITerminal): IDisposable { // Winpty does not support wraparound mode which means that lines will never diff --git a/src/BufferLine.ts b/src/core/buffer/BufferLine.ts similarity index 97% rename from src/BufferLine.ts rename to src/core/buffer/BufferLine.ts index 39b6116d..154cc598 100644 --- a/src/BufferLine.ts +++ b/src/core/buffer/BufferLine.ts @@ -2,9 +2,9 @@ * Copyright (c) 2018 The xterm.js authors. All rights reserved. * @license MIT */ -import { CharData, IBufferLine, ICellData, IColorRGB, IAttributeData } from './core/Types'; -import { stringFromCodePoint } from './core/input/TextDecoder'; -import { DEFAULT_COLOR } from './common/Types'; +import { CharData, IBufferLine, ICellData, IColorRGB, IAttributeData } from '../Types'; +import { stringFromCodePoint } from '../input/TextDecoder'; +import { DEFAULT_COLOR } from '../../common/Types'; export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); @@ -325,17 +325,15 @@ export class CellData extends AttributeData implements ICellData { * memory allocs / GC pressure can be greatly reduced by reusing the CellData object. */ export class BufferLine implements IBufferLine { - protected _data: Uint32Array | null = null; + protected _data: Uint32Array; protected _combined: {[index: number]: string} = {}; public length: number; constructor(cols: number, fillCellData?: ICellData, public isWrapped: boolean = false) { - if (cols) { - this._data = new Uint32Array(cols * CELL_SIZE); - const cell = fillCellData || CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); - for (let i = 0; i < cols; ++i) { - this.setCell(i, cell); - } + this._data = new Uint32Array(cols * CELL_SIZE); + const cell = fillCellData || CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + for (let i = 0; i < cols; ++i) { + this.setCell(i, cell); } this.length = cols; } @@ -573,7 +571,7 @@ export class BufferLine implements IBufferLine { } } } else { - this._data = null; + this._data = new Uint32Array(0); this._combined = {}; } } diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 63846155..ce50a651 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -10,7 +10,7 @@ import { DEFAULT_COLOR } from '../common/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/Types'; import BaseCharAtlas from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; -import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../BufferLine'; +import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../core/buffer/BufferLine'; import { JoinedCellData } from './CharacterJoinerRegistry'; export abstract class BaseRenderLayer implements IRenderLayer { diff --git a/src/renderer/CharacterJoinerRegistry.test.ts b/src/renderer/CharacterJoinerRegistry.test.ts index 590d6a5f..948985a4 100644 --- a/src/renderer/CharacterJoinerRegistry.test.ts +++ b/src/renderer/CharacterJoinerRegistry.test.ts @@ -10,7 +10,7 @@ import { CircularList } from '../common/CircularList'; import { ICharacterJoinerRegistry } from './Types'; import { CharacterJoinerRegistry } from './CharacterJoinerRegistry'; -import { BufferLine, CellData } from '../BufferLine'; +import { BufferLine, CellData } from '../core/buffer/BufferLine'; import { IBufferLine } from '../core/Types'; describe('CharacterJoinerRegistry', () => { diff --git a/src/renderer/CharacterJoinerRegistry.ts b/src/renderer/CharacterJoinerRegistry.ts index 33cd8936..3302c312 100644 --- a/src/renderer/CharacterJoinerRegistry.ts +++ b/src/renderer/CharacterJoinerRegistry.ts @@ -6,7 +6,7 @@ import { ITerminal } from '../Types'; import { IBufferLine, ICellData, CharData } from '../core/Types'; import { ICharacterJoinerRegistry, ICharacterJoiner } from './Types'; -import { CellData, Content, AttributeData, WHITESPACE_CELL_CHAR } from '../BufferLine'; +import { CellData, Content, AttributeData, WHITESPACE_CELL_CHAR } from '../core/buffer/BufferLine'; export class JoinedCellData extends AttributeData implements ICellData { private _width: number; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 72aaa447..3e8af02d 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -7,7 +7,7 @@ import { IColorSet, IRenderDimensions } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { ITerminal } from '../Types'; import { ICellData } from '../core/Types'; -import { CellData } from '../BufferLine'; +import { CellData } from '../core/buffer/BufferLine'; interface ICursorState { x: number; diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 90c7e267..8be5665c 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -8,7 +8,7 @@ import { ITerminal } from '../Types'; import { CharData, ICellData } from '../core/Types'; import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { CellData, AttributeData, Content, NULL_CELL_CODE } from '../BufferLine'; +import { CellData, AttributeData, Content, NULL_CELL_CODE } from '../core/buffer/BufferLine'; import { JoinedCellData } from './CharacterJoinerRegistry'; /** diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index f009407d..b2530595 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -6,7 +6,7 @@ import jsdom = require('jsdom'); import { assert } from 'chai'; import { DomRendererRowFactory } from './DomRendererRowFactory'; -import { BufferLine, CellData, FgFlags, BgFlags, Attributes, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, DEFAULT_ATTR_DATA } from '../../BufferLine'; +import { BufferLine, CellData, FgFlags, BgFlags, Attributes, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, DEFAULT_ATTR_DATA } from '../../core/buffer/BufferLine'; import { ITerminalOptions } from '../../Types'; import { IBufferLine } from '../../core/Types'; diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 481d096e..85ef586e 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -6,7 +6,7 @@ import { ITerminalOptions } from '../../Types'; import { IBufferLine } from '../../core/Types'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; -import { CellData, AttributeData, NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../BufferLine'; +import { CellData, AttributeData, NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../core/buffer/BufferLine'; export const BOLD_CLASS = 'xterm-bold'; export const DIM_CLASS = 'xterm-dim'; From 59aec69379c67d114e4d1bdf0f5e28e00cba72b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 9 May 2019 22:19:33 -0700 Subject: [PATCH 36/97] Add BufferLine into core and strict null check --- src/{ => core/buffer}/BufferLine.test.ts | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) rename src/{ => core/buffer}/BufferLine.test.ts (95%) diff --git a/src/BufferLine.test.ts b/src/core/buffer/BufferLine.test.ts similarity index 95% rename from src/BufferLine.test.ts rename to src/core/buffer/BufferLine.test.ts index 87a4e631..c42b372e 100644 --- a/src/BufferLine.test.ts +++ b/src/core/buffer/BufferLine.test.ts @@ -3,8 +3,8 @@ * @license MIT */ import * as chai from 'chai'; -import { BufferLine, CellData, Content, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './core/buffer/BufferLine'; -import { CharData, IBufferLine } from './core/Types'; +import { BufferLine, CellData, Content, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './BufferLine'; +import { CharData, IBufferLine } from '../Types'; class TestBufferLine extends BufferLine { public get combined(): {[index: number]: string} { @@ -55,7 +55,7 @@ describe('BufferLine', function(): void { chai.expect(line.length).equals(10); chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); chai.expect(line.isWrapped).equals(false); - line = new TestBufferLine(10, null, true); + line = new TestBufferLine(10, undefined, true); chai.expect(line.length).equals(10); chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); chai.expect(line.isWrapped).equals(true); @@ -125,7 +125,7 @@ describe('BufferLine', function(): void { ]); }); it('clone', function(): void { - const line = new TestBufferLine(5, null, true); + const line = new TestBufferLine(5, undefined, true); line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)])); line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)])); @@ -165,27 +165,27 @@ describe('BufferLine', function(): void { it('enlarge(false)', function(): void { const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + chai.expect(line.toArray()).eql((Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('enlarge(true)', function(): void { const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + chai.expect(line.toArray()).eql((Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink(true) - should apply new size', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - chai.expect(line.toArray()).eql(Array(5).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + chai.expect(line.toArray()).eql((Array(5) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('shrink to 0 length', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); line.resize(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - chai.expect(line.toArray()).eql(Array(0).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + chai.expect(line.toArray()).eql((Array(0) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); it('should remove combining data on replaced cells after shrinking then enlarging', () => { const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); - line.set(2, [ null, '😁', 1, '😁'.charCodeAt(0) ]); - line.set(9, [ null, '😁', 1, '😁'.charCodeAt(0) ]); + line.set(2, [ 0, '😁', 1, '😁'.charCodeAt(0) ]); + line.set(9, [ 0, '😁', 1, '😁'.charCodeAt(0) ]); chai.expect(line.translateToString()).eql('aa😁aaaaaa😁'); chai.expect(Object.keys(line.combined).length).eql(2); line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); @@ -222,7 +222,7 @@ describe('BufferLine', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); line.setCell(2, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)])); - line.setCell(3, CellData.fromCharData([0, '', 0, undefined])); + line.setCell(3, CellData.fromCharData([0, '', 0, 0])); chai.expect(line.getTrimmedLength()).equal(4); // also counts null cell after fullwidth }); }); @@ -282,11 +282,11 @@ describe('BufferLine', function(): void { const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); line.setCell(2, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)])); - line.setCell(3, CellData.fromCharData([0, '', 0, undefined])); + line.setCell(3, CellData.fromCharData([0, '', 0, 0])); line.setCell(5, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)])); - line.setCell(6, CellData.fromCharData([0, '', 0, undefined])); + line.setCell(6, CellData.fromCharData([0, '', 0, 0])); line.setCell(7, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)])); - line.setCell(8, CellData.fromCharData([0, '', 0, undefined])); + line.setCell(8, CellData.fromCharData([0, '', 0, 0])); chai.expect(line.translateToString(false)).equal('a 1 11 '); chai.expect(line.translateToString(true)).equal('a 1 11'); chai.expect(line.translateToString(false, 0, 7)).equal('a 1 1'); From d735196a305bbce8ae05404d747fc4642f1439b9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 07:39:42 -0700 Subject: [PATCH 37/97] Make sure beta builds are always on patch version 0 --- bin/publish.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/publish.js b/bin/publish.js index 3b13d6a5..9df2e982 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -39,7 +39,7 @@ function getNextBetaVersion() { } const tag = 'beta'; const stableVersion = packageJson.version.split('.'); - const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.${stableVersion[2]}`; + const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`; const publishedVersions = getPublishedVersions(nextStableVersion, tag); if (publishedVersions.length === 0) { return `${nextStableVersion}-${tag}1`; @@ -54,7 +54,7 @@ function getNextBetaVersion() { } function getPublishedVersions(version, tag) { - const versionsProcess = cp.spawnSync('npm', ['view', 'xterm', 'versions', '--json']); + const versionsProcess = cp.spawnSync('npm', ['view', packageJson.name, 'versions', '--json']); const versionsJson = JSON.parse(versionsProcess.stdout); if (tag) { return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}[0-9]+`))); From b658baf921d1f8e4e26af868744c43082684d825 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 16:26:48 -0700 Subject: [PATCH 38/97] Add typings for windowsMode --- fixtures/typings-test/typings-test.ts | 8 +++++--- typings/xterm.d.ts | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts index 87d911c2..65c478c6 100644 --- a/fixtures/typings-test/typings-test.ts +++ b/fixtures/typings-test/typings-test.ts @@ -21,7 +21,7 @@ namespace constructor { 'disableStdin': false, 'rows': 1, 'scrollback': 10, - 'tabStopWidth': 2, + 'tabStopWidth': 2 }); } } @@ -119,8 +119,8 @@ namespace methods_core { const t: Terminal = new Terminal(); t.attachCustomKeyEventHandler((e: KeyboardEvent) => true); t.attachCustomKeyEventHandler((e: KeyboardEvent) => false); - const d1: IDisposable = t.addCsiHandler("x", - (params: number[], collect: string): boolean => params[0]===1); + const d1: IDisposable = t.addCsiHandler('x', + (params: number[], collect: string): boolean => params[0] === 1); d1.dispose(); const d2: IDisposable = t.addOscHandler(199, (data: string): boolean => true); @@ -155,6 +155,7 @@ namespace methods_core { const r25: string = t.getOption('fontWeightBold'); const r26: boolean = t.getOption('allowTransparency'); const r27: boolean = t.getOption('rightClickSelectsWord'); + const r28: boolean = t.getOption('windowsMode'); } { const t: Terminal = new Terminal(); @@ -177,6 +178,7 @@ namespace methods_core { t.setOption('useFlowControl', true); t.setOption('allowTransparency', true); t.setOption('visualBell', true); + t.setOption('windowsMode', true); t.setOption('colors', ['a', 'b']); t.setOption('letterSpacing', 1); t.setOption('cols', 1); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 9f1fbe62..d610d684 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -747,7 +747,7 @@ declare module 'xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean; + getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode'): boolean; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -798,7 +798,7 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void; + setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode', value: boolean): void; /** * Sets an option on the terminal. * @param key The option key. From 518e2734bacf8fdb7f84e0e745e9ad7ed09151f4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 20:27:53 -0700 Subject: [PATCH 39/97] Re-introduce original attach and webLinks addons back We could release this in v3 if we don't break addons like this --- src/addons/attach/Interfaces.ts | 23 +++ src/addons/attach/attach.test.ts | 20 +++ src/addons/attach/attach.ts | 157 ++++++++++++++++++++ src/addons/attach/index.html | 93 ++++++++++++ src/addons/attach/package.json | 5 + src/addons/attach/tsconfig.json | 19 +++ src/addons/webLinks/package.json | 5 + src/addons/webLinks/tsconfig.json | 22 +++ src/addons/webLinks/webLinks.test.ts | 212 +++++++++++++++++++++++++++ src/addons/webLinks/webLinks.ts | 47 ++++++ src/public/Terminal.test.ts | 17 +++ src/tsconfig.all.json | 2 + 12 files changed, 622 insertions(+) create mode 100644 src/addons/attach/Interfaces.ts create mode 100644 src/addons/attach/attach.test.ts create mode 100644 src/addons/attach/attach.ts create mode 100644 src/addons/attach/index.html create mode 100644 src/addons/attach/package.json create mode 100644 src/addons/attach/tsconfig.json create mode 100644 src/addons/webLinks/package.json create mode 100644 src/addons/webLinks/tsconfig.json create mode 100644 src/addons/webLinks/webLinks.test.ts create mode 100644 src/addons/webLinks/webLinks.ts create mode 100644 src/public/Terminal.test.ts diff --git a/src/addons/attach/Interfaces.ts b/src/addons/attach/Interfaces.ts new file mode 100644 index 00000000..4b269099 --- /dev/null +++ b/src/addons/attach/Interfaces.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + * + * Implements the attach method, that attaches the terminal to a WebSocket stream. + */ + +import { Terminal, IDisposable } from 'xterm'; + +export interface IAttachAddonTerminal extends Terminal { + _core: { + register(d: T): void; + }; + + __socket?: WebSocket; + __attachSocketBuffer?: string; + __dataListener?: IDisposable; + + __getMessage?(ev: MessageEvent): void; + __flushBuffer?(): void; + __pushToBuffer?(data: string): void; + __sendData?(data: string): void; +} diff --git a/src/addons/attach/attach.test.ts b/src/addons/attach/attach.test.ts new file mode 100644 index 00000000..e280b656 --- /dev/null +++ b/src/addons/attach/attach.test.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; + +import * as attach from './attach'; + +class MockTerminal {} + +describe('attach addon', () => { + describe('apply', () => { + it('should do register the `attach` and `detach` methods', () => { + attach.apply(MockTerminal); + assert.equal(typeof (MockTerminal).prototype.attach, 'function'); + assert.equal(typeof (MockTerminal).prototype.detach, 'function'); + }); + }); +}); diff --git a/src/addons/attach/attach.ts b/src/addons/attach/attach.ts new file mode 100644 index 00000000..2c8a5d4d --- /dev/null +++ b/src/addons/attach/attach.ts @@ -0,0 +1,157 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * @license MIT + * + * Implements the attach method, that attaches the terminal to a WebSocket stream. + */ + +import { Terminal, IDisposable } from 'xterm'; +import { IAttachAddonTerminal } from './Interfaces'; + +/** + * Attaches the given terminal to the given socket. + * + * @param term The terminal to be attached to the given socket. + * @param socket The socket to attach the current terminal. + * @param bidirectional Whether the terminal should send data to the socket as well. + * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum + * frequency of 1 rendering per 10ms. + */ +export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void { + const addonTerminal = term; + bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional; + addonTerminal.__socket = socket; + + addonTerminal.__flushBuffer = () => { + addonTerminal.write(addonTerminal.__attachSocketBuffer); + addonTerminal.__attachSocketBuffer = null; + }; + + addonTerminal.__pushToBuffer = (data: string) => { + if (addonTerminal.__attachSocketBuffer) { + addonTerminal.__attachSocketBuffer += data; + } else { + addonTerminal.__attachSocketBuffer = data; + setTimeout(addonTerminal.__flushBuffer, 10); + } + }; + + // TODO: This should be typed but there seem to be issues importing the type + let myTextDecoder: any; + + addonTerminal.__getMessage = function(ev: MessageEvent): void { + let str: string; + + if (typeof ev.data === 'object') { + if (!myTextDecoder) { + myTextDecoder = new TextDecoder(); + } + if (ev.data instanceof ArrayBuffer) { + str = myTextDecoder.decode(ev.data); + displayData(str); + } else { + const fileReader = new FileReader(); + + fileReader.addEventListener('load', () => { + str = myTextDecoder.decode(fileReader.result); + displayData(str); + }); + fileReader.readAsArrayBuffer(ev.data); + } + } else if (typeof ev.data === 'string') { + displayData(ev.data); + } else { + throw Error(`Cannot handle "${typeof ev.data}" websocket message.`); + } + }; + + /** + * Push data to buffer or write it in the terminal. + * This is used as a callback for FileReader.onload. + * + * @param str String decoded by FileReader. + * @param data The data of the EventMessage. + */ + function displayData(str?: string, data?: string): void { + if (buffered) { + addonTerminal.__pushToBuffer(str || data); + } else { + addonTerminal.write(str || data); + } + } + + addonTerminal.__sendData = (data: string) => { + if (socket.readyState !== 1) { + return; + } + socket.send(data); + }; + + addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage)); + + if (bidirectional) { + addonTerminal.__dataListener = addonTerminal.onData(addonTerminal.__sendData); + addonTerminal._core.register(addonTerminal.__dataListener); + } + + addonTerminal._core.register(addSocketListener(socket, 'close', () => detach(addonTerminal, socket))); + addonTerminal._core.register(addSocketListener(socket, 'error', () => detach(addonTerminal, socket))); +} + +function addSocketListener(socket: WebSocket, type: string, handler: (this: WebSocket, ev: Event) => any): IDisposable { + socket.addEventListener(type, handler); + return { + dispose: () => { + if (!handler) { + // Already disposed + return; + } + socket.removeEventListener(type, handler); + handler = null; + } + }; +} + +/** + * Detaches the given terminal from the given socket + * + * @param term The terminal to be detached from the given socket. + * @param socket The socket from which to detach the current terminal. + */ +export function detach(term: Terminal, socket: WebSocket): void { + const addonTerminal = term; + addonTerminal.__dataListener.dispose(); + addonTerminal.__dataListener = undefined; + + socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket; + + if (socket) { + socket.removeEventListener('message', addonTerminal.__getMessage); + } + + delete addonTerminal.__socket; +} + + +export function apply(terminalConstructor: typeof Terminal): void { + /** + * Attaches the current terminal to the given socket + * + * @param socket The socket to attach the current terminal. + * @param bidirectional Whether the terminal should send data to the socket as well. + * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum + * frequency of 1 rendering per 10ms. + */ + (terminalConstructor.prototype).attach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void { + attach(this, socket, bidirectional, buffered); + }; + + /** + * Detaches the current terminal from the given socket. + * + * @param socket The socket from which to detach the current terminal. + */ + (terminalConstructor.prototype).detach = function (socket: WebSocket): void { + detach(this, socket); + }; +} diff --git a/src/addons/attach/index.html b/src/addons/attach/index.html new file mode 100644 index 00000000..b6f853be --- /dev/null +++ b/src/addons/attach/index.html @@ -0,0 +1,93 @@ + + + + + + + + + + +
+ +

+ xterm.js: socket attach +

+

+ Attach the terminal to a WebSocket terminal stream with ease. Perfect for attaching to your + Docker containers. +

+

+ Socket information +

+
+ + +
+
+ +
+ + + \ No newline at end of file diff --git a/src/addons/attach/package.json b/src/addons/attach/package.json new file mode 100644 index 00000000..9e45068b --- /dev/null +++ b/src/addons/attach/package.json @@ -0,0 +1,5 @@ +{ + "name": "xterm.attach", + "main": "attach.js", + "private": true +} diff --git a/src/addons/attach/tsconfig.json b/src/addons/attach/tsconfig.json new file mode 100644 index 00000000..2f39102c --- /dev/null +++ b/src/addons/attach/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "lib": [ + "dom", + "es6", + ], + "rootDir": ".", + "outDir": "../../../lib/addons/attach/", + "sourceMap": true, + "removeComments": true, + "declaration": true + }, + "include": [ + "**/*.ts", + "../../../typings/xterm.d.ts" + ] +} diff --git a/src/addons/webLinks/package.json b/src/addons/webLinks/package.json new file mode 100644 index 00000000..f200cab4 --- /dev/null +++ b/src/addons/webLinks/package.json @@ -0,0 +1,5 @@ +{ + "name": "xterm.weblinks", + "main": "weblinks.js", + "private": true +} diff --git a/src/addons/webLinks/tsconfig.json b/src/addons/webLinks/tsconfig.json new file mode 100644 index 00000000..9c4f1176 --- /dev/null +++ b/src/addons/webLinks/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "lib": [ + "dom", + "es5", + ], + "rootDir": ".", + "outDir": "../../../lib/addons/webLinks/", + "sourceMap": true, + "removeComments": true, + "declaration": true, + "types": [ + "../../node_modules/@types/mocha" + ] + }, + "include": [ + "**/*.ts", + "../../../typings/xterm.d.ts" + ] +} diff --git a/src/addons/webLinks/webLinks.test.ts b/src/addons/webLinks/webLinks.test.ts new file mode 100644 index 00000000..da5569ab --- /dev/null +++ b/src/addons/webLinks/webLinks.test.ts @@ -0,0 +1,212 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; + +import * as webLinks from './webLinks'; + +class MockTerminal { + public regex: RegExp; + public handler: (event: MouseEvent, uri: string) => void; + public options?: any; + + public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: any): number { + this.regex = regex; + this.handler = handler; + this.options = options; + return 0; + } +} + +describe('webLinks addon', () => { + describe('apply', () => { + it('should do register the `webLinksInit` method', () => { + webLinks.apply(MockTerminal); + assert.equal(typeof (MockTerminal).prototype.webLinksInit, 'function'); + }); + }); + + describe('should allow simple URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://foo.com '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://foo.com'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://bar.io '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io'); + }); + }); + + describe('should allow ~ character in URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://foo.com/a~b#c~d?e~f '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://foo.com/a~b#c~d?e~f'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://bar.io/a~b#c~d?e~f '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/a~b#c~d?e~f'); + }); + }); + + describe('should allow : character in URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://foo.com/colon:test '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://foo.com/colon:test'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://bar.io/colon:test '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/colon:test'); + }); + }); + + describe('should not allow : character at the end of a URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://foo.com/colon:test: '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://foo.com/colon:test'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = ' http://bar.io/colon:test: '; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/colon:test'); + }); + }); + + describe('should not allow " character at the end of a URI enclosed with ""', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = '"http://foo.com/"'; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://foo.com/'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = '"http://bar.io/"'; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/'); + }); + }); + + describe('should not allow \' character at the end of a URI enclosed with \'\'', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = '\'http://foo.com/\''; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://foo.com/'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = '\'http://bar.io/\''; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/'); + }); + }); + + describe('should allow + character in URI path', () => { + it('foo.com', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = 'http://foo.com/subpath/+/id'; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://foo.com/subpath/+/id'); + }); + + it('bar.io', () => { + const term = new MockTerminal(); + webLinks.webLinksInit(term); + + const row = 'http://bar.io/subpath/+/id'; + + const match = row.match(term.regex); + const uri = match[term.options.matchIndex]; + + assert.equal(uri, 'http://bar.io/subpath/+/id'); + }); + }); +}); diff --git a/src/addons/webLinks/webLinks.ts b/src/addons/webLinks/webLinks.ts new file mode 100644 index 00000000..8a0fec09 --- /dev/null +++ b/src/addons/webLinks/webLinks.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { Terminal, ILinkMatcherOptions } from 'xterm'; + +const protocolClause = '(https?:\\/\\/)'; +const domainCharacterSet = '[\\da-z\\.-]+'; +const negatedDomainCharacterSet = '[^\\da-z\\.-]+'; +const domainBodyClause = '(' + domainCharacterSet + ')'; +const tldClause = '([a-z\\.]{2,6})'; +const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})'; +const localHostClause = '(localhost)'; +const portClause = '(:\\d{1,5})'; +const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?'; +const pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:+]*)*([^:"\'\\s])'; +const pathClause = '(' + pathCharacterSet + ')?'; +const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; +const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; +const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?'; +const negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+'; +const bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause; +const start = '(?:^|' + negatedDomainCharacterSet + ')('; +const end = ')($|' + negatedPathCharacterSet + ')'; +const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end); + +function handleLink(event: MouseEvent, uri: string): void { + window.open(uri, '_blank'); +} + +/** + * Initialize the web links addon, registering the link matcher. + * @param term The terminal to use web links within. + * @param handler A custom handler to use. + * @param options Custom options to use, matchIndex will always be ignored. + */ +export function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void { + options.matchIndex = 1; + term.registerLinkMatcher(strictUrlRegex, handler, options); +} + +export function apply(terminalConstructor: typeof Terminal): void { + (terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void { + webLinksInit(this, handler, options); + }; +} diff --git a/src/public/Terminal.test.ts b/src/public/Terminal.test.ts new file mode 100644 index 00000000..6bad5b04 --- /dev/null +++ b/src/public/Terminal.test.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { Terminal } from './Terminal'; +import * as attach from '../addons/attach/attach'; + + describe('Terminal', () => { + it('should apply addons with Terminal.applyAddon', () => { + Terminal.applyAddon(attach); + // Test that addon was applied successfully, adding attach to Terminal's + // prototype. + assert.equal(typeof (Terminal).prototype.attach, 'function'); + }); +}); diff --git a/src/tsconfig.all.json b/src/tsconfig.all.json index 55689ad8..2a53ab89 100644 --- a/src/tsconfig.all.json +++ b/src/tsconfig.all.json @@ -3,10 +3,12 @@ "include": [], "references": [ { "path": "." }, + { "path": "./addons/attach" }, { "path": "./addons/fit" }, { "path": "./addons/fullscreen" }, { "path": "./addons/search" }, { "path": "./addons/terminado" }, + { "path": "./addons/webLinks" }, { "path": "./addons/zmodem" } ] } From d6960469216d827c94f72c3e81ab3f389a8214c8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 20:38:08 -0700 Subject: [PATCH 40/97] Simplify and mark new addon API as experimental --- typings/xterm.d.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index cd7bcf0d..817da6ca 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -858,22 +858,16 @@ declare module 'xterm' { static applyAddon(addon: any): void; /** - * Loads an addon into this instance of xterm.js. + * (EXPERIMENTAL) Loads an addon into this instance of xterm.js. * @param addon The addon to load. */ loadAddon(addon: ITerminalAddon): void; } - export interface ITerminalAddon { + export interface ITerminalAddon extends IDisposable { /** - * This is called when the addon is activated within xterm.js. + * (EXPERIMENTAL) This is called when the addon is activated within xterm.js. */ activate(terminal: Terminal): void; - - /** - * This function includes anything that needs to happen to clean up when - * the addon is being disposed. - */ - dispose(): void; } } From c04ebe974fa6d584c1c0c7af524ace204a706b53 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 20:51:18 -0700 Subject: [PATCH 41/97] Fix bad import from lib Related #1996 --- src/Buffer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index c1c08c85..c42b9726 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -10,7 +10,7 @@ import { BufferLine, CellData, AttributeData } from './BufferLine'; import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from './BufferReflow'; import { DEFAULT_COLOR } from './renderer/atlas/Types'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; -import { Disposable } from '../lib/common/Lifecycle'; +import { Disposable } from './common/Lifecycle'; export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); From 16aaf6c58c5209e879343996de6d0130c6cd3043 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 21:08:06 -0700 Subject: [PATCH 42/97] Fix build --- src/Types.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Types.ts b/src/Types.ts index a6451cfd..5c276bf2 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -227,6 +227,15 @@ export interface IPublicTerminal extends IDisposable, IEventEmitter { cols: number; buffer: IBuffer; markers: IMarker[]; + onCursorMove: IEvent; + onData: IEvent; + onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; + onLineFeed: IEvent; + onScroll: IEvent; + onSelectionChange: IEvent; + onRender: IEvent<{ start: number, end: number }>; + onResize: IEvent<{ cols: number, rows: number }>; + onTitleChange: IEvent; blur(): void; focus(): void; resize(columns: number, rows: number): void; From 23bdb2a6c20d1df074ad54a92550ef4862b6669f Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Sat, 11 May 2019 00:59:59 -0400 Subject: [PATCH 43/97] fix: don't use Math.floor in DomRenderer charWidth computation Fixes #2063 --- src/renderer/dom/DomRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 01d5c7dc..c192034c 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -101,7 +101,7 @@ export class DomRenderer extends Disposable implements IRenderer { } private _updateDimensions(): void { - this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * window.devicePixelRatio); + this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio; this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio); this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing); this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight); From 55d590348c6a39ab4c96fcd7cb9e51a4cb1e111f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 22:57:27 -0700 Subject: [PATCH 44/97] API integration tests Part of #1247 --- azure-pipelines.yml | 11 +++-- demo/client.ts | 10 ++-- demo/server.js | 4 ++ demo/test.html | 12 +++++ package.json | 5 ++ src/public/Terminal.api.ts | 99 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 demo/test.html create mode 100644 src/public/Terminal.api.ts diff --git a/azure-pipelines.yml b/azure-pipelines.yml index db74a4ea..fd65a6a8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -21,7 +21,12 @@ jobs: displayName: 'Install dependencies and build' - script: | yarn mocha - displayName: 'Test' + displayName: 'Unit tests' + - script: | + yarn start & + sleep 5 + yarn test-api --headless + displayName: 'Integration tests' - script: | yarn lint displayName: 'Lint' @@ -39,7 +44,7 @@ jobs: displayName: 'Install dependencies and build' - script: | yarn mocha - displayName: 'Test' + displayName: 'Unit tests' - script: | yarn lint displayName: 'Lint' @@ -62,7 +67,7 @@ jobs: displayName: 'Install dependencies and build' - script: | yarn mocha - displayName: 'Test' + displayName: 'Unit tests' - script: | yarn lint displayName: 'Lint' diff --git a/demo/client.ts b/demo/client.ts index 90a913d7..b7409109 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -21,6 +21,7 @@ import { Terminal as TerminalType, ITerminalOptions } from 'xterm'; export interface IWindowWithTerminal extends Window { term: TerminalType; + Terminal?: typeof TerminalType; } declare let window: IWindowWithTerminal; @@ -57,8 +58,6 @@ function getSearchOptions(): ISearchOptions { }; } -createTerminal(); - const disposeRecreateButtonHandler = () => { // If the terminal exists dispose of it, otherwise recreate it if (term) { @@ -74,7 +73,12 @@ const disposeRecreateButtonHandler = () => { } }; -document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler); +if (document.location.pathname === '/test') { + window.Terminal = Terminal; +} else { + createTerminal(); + document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler); +} function createTerminal(): void { // Clean terminal diff --git a/demo/server.js b/demo/server.js index 758023c7..8270a398 100644 --- a/demo/server.js +++ b/demo/server.js @@ -16,6 +16,10 @@ function startServer() { res.sendFile(__dirname + '/index.html'); }); + app.get('/test', function(req, res){ + res.sendFile(__dirname + '/test.html'); + }); + app.get('/style.css', function(req, res){ res.sendFile(__dirname + '/style.css'); }); diff --git a/demo/test.html b/demo/test.html new file mode 100644 index 00000000..275c542d --- /dev/null +++ b/demo/test.html @@ -0,0 +1,12 @@ + + + + xterm.js integration test fixture + + + + +
+ + + diff --git a/package.json b/package.json index 1727af82..615962c4 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@types/jsdom": "11.0.1", "@types/mocha": "^2.2.33", "@types/node": "6.0.108", + "@types/puppeteer": "^1.12.4", "@types/webpack": "^4.4.11", "browserify": "^13.3.0", "chai": "3.5.0", @@ -54,6 +55,7 @@ "test-debug": "node --inspect-brk node_modules/.bin/gulp test", "test-suite": "gulp mocha-suite --test", "test-coverage": "nyc -x gulpfile.js -x '**/*test*' npm run mocha", + "test-api": "mocha \"**/*.api.js\"", "mocha": "gulp test", "prebuild": "tsc -b ./src/tsconfig.all.json", "build": "gulp build", @@ -61,5 +63,8 @@ "prepublishOnly": "npm run build", "coveralls": "nyc report --reporter=text-lcov | coveralls", "watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput" + }, + "dependencies": { + "puppeteer": "^1.15.0" } } diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts new file mode 100644 index 00000000..168a9407 --- /dev/null +++ b/src/public/Terminal.api.ts @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import * as puppeteer from 'puppeteer'; +import { assert } from 'chai'; +import { ITerminalOptions } from '../Types'; + +const APP = 'http://127.0.0.1:3000/test'; + +let browser: puppeteer.Browser; +let page: puppeteer.Page; +const width = 800; +const height = 600; + +describe('API Integration Tests', () => { + before(async function(): Promise { + this.timeout(10000); + browser = await puppeteer.launch({ + headless: process.argv.indexOf('--headless') !== -1, + slowMo: 80, + args: [`--window-size=${width},${height}`] + }); + page = (await browser.pages())[0]; + await page.setViewport({ width, height }); + }); + + after(() => { + browser.close(); + }); + + beforeEach(async () => { + await page.goto(APP); + }); + + it('Default options', async function(): Promise { + this.timeout(10000); + await openTerminal(); + assert.equal(await page.evaluate(`window.term.cols`), 80); + assert.equal(await page.evaluate(`window.term.rows`), 24); + }); + + it('write', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.term.write('foo'); + window.term.write('bar'); + `); + assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(0, true)`), 'foobar'); + }); + + it('writeln', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.term.writeln('foo'); + window.term.writeln('bar'); + `); + assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(0, true)`), 'foo'); + assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(1, true)`), 'bar'); + }); + + it('clear', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5 }); + await page.evaluate(` + window.term.write('test0'); + for (let i = 1; i < 10; i++) { + window.term.write('\\n\\rtest' + i); + } + `); + await page.evaluate(`window.term.clear()`); + assert.equal(await page.evaluate(`window.term._core.buffer.lines.length`), '5'); + assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(0, true)`), 'test9'); + for (let i = 1; i < 5; i++) { + assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(${i}, true)`), ''); + } + }); + + it('getOption, setOption', async function(): Promise { + this.timeout(10000); + await openTerminal(); + assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'canvas'); + await page.evaluate(`window.term.setOption('rendererType', 'dom')`); + assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'dom'); + }); +}); + +async function openTerminal(options: ITerminalOptions = {}): Promise { + await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); + if (options.rendererType === 'dom') { + await page.waitForSelector('.xterm-rows'); + } else { + await page.waitForSelector('.xterm-text-layer'); + } +} From 8a5b6b0356e69ac5433d65929dbb80230d92dd60 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 22:58:23 -0700 Subject: [PATCH 45/97] Move puppeteer to devDependencies --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 615962c4..68d72a1b 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "node-pty": "0.7.6", "nodemon": "1.10.2", "nyc": "^11.8.0", + "puppeteer": "^1.15.0", "sorcery": "^0.10.0", "source-map-loader": "^0.2.4", "ts-loader": "^4.5.0", @@ -63,8 +64,5 @@ "prepublishOnly": "npm run build", "coveralls": "nyc report --reporter=text-lcov | coveralls", "watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput" - }, - "dependencies": { - "puppeteer": "^1.15.0" } } From 97b176e70109d49637cdb3d60cea51e08e1f03f1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 23:14:16 -0700 Subject: [PATCH 46/97] Add selection tests --- src/public/Terminal.api.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 168a9407..00a6e6bf 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -86,6 +86,20 @@ describe('API Integration Tests', () => { await page.evaluate(`window.term.setOption('rendererType', 'dom')`); assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'dom'); }); + + it('selection', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5 }); + await page.evaluate(`window.term.write('\\n\\nfoo\\n\\n\\rbar\\n\\n\\rbaz')`); + assert.equal(await page.evaluate(`window.term.hasSelection()`), false); + assert.equal(await page.evaluate(`window.term.getSelection()`), ''); + await page.evaluate(`window.term.selectAll()`); + assert.equal(await page.evaluate(`window.term.hasSelection()`), true); + assert.equal(await page.evaluate(`window.term.getSelection()`), '\n\nfoo\n\nbar\n\nbaz'); + await page.evaluate(`window.term.clearSelection()`); + assert.equal(await page.evaluate(`window.term.hasSelection()`), false); + assert.equal(await page.evaluate(`window.term.getSelection()`), ''); + }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { From 34ac1bc5029ea7f440acaf904c9c502324d6b1c9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 23:38:45 -0700 Subject: [PATCH 47/97] Fix clearSelection not firing onSelectionChange Fixes #2070 --- src/SelectionManager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index dcd60068..71255083 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -245,6 +245,7 @@ export class SelectionManager implements ISelectionManager { this._model.clearSelection(); this._removeMouseDownListeners(); this.refresh(); + this._onSelectionChange.fire(); } /** From 7562948259f1d9ef5776acdc498d9228e27e74e4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 23:47:28 -0700 Subject: [PATCH 48/97] Add tests for all API events --- src/public/Terminal.api.ts | 121 +++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 00a6e6bf..147519f4 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -100,6 +100,127 @@ describe('API Integration Tests', () => { assert.equal(await page.evaluate(`window.term.hasSelection()`), false); assert.equal(await page.evaluate(`window.term.getSelection()`), ''); }); + + describe('Events', () => { + it('onCursorMove', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.callCount = 0; + window.term.onCursorMove(e => window.callCount++); + window.term.write('foo'); + `); + assert.equal(await page.evaluate(`window.callCount`), 1); + await page.evaluate(`window.term.write('bar')`); + assert.equal(await page.evaluate(`window.callCount`), 2); + }); + + it('onData', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.calls = []; + window.term.onData(e => calls.push(e)); + `); + await page.type('.xterm-helper-textarea', 'foo'); + assert.deepEqual(await page.evaluate(`window.calls`), ['f', 'o', 'o']); + }); + + it('onKey', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.calls = []; + window.term.onKey(e => calls.push(e.key)); + `); + await page.type('.xterm-helper-textarea', 'foo'); + assert.deepEqual(await page.evaluate(`window.calls`), ['f', 'o', 'o']); + }); + + it('onLineFeed', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.callCount = 0; + window.term.onLineFeed(() => callCount++); + window.term.writeln('foo'); + `); + assert.equal(await page.evaluate(`window.callCount`), 1); + await page.evaluate(`window.term.writeln('bar')`); + assert.equal(await page.evaluate(`window.callCount`), 2); + }); + + it('onScroll', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5 }); + await page.evaluate(` + window.calls = []; + window.term.onScroll(e => window.calls.push(e)); + for (let i = 0; i < 4; i++) { + window.term.writeln('foo'); + } + `); + assert.deepEqual(await page.evaluate(`window.calls`), []); + await page.evaluate(`window.term.writeln('bar')`); + assert.deepEqual(await page.evaluate(`window.calls`), [1]); + await page.evaluate(`window.term.writeln('baz')`); + assert.deepEqual(await page.evaluate(`window.calls`), [1, 2]); + }); + + it('onSelectionChange', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.callCount = 0; + window.term.onSelectionChange(() => window.callCount++); + `); + assert.equal(await page.evaluate(`window.callCount`), 0); + await page.evaluate(`window.term.selectAll()`); + assert.equal(await page.evaluate(`window.callCount`), 1); + await page.evaluate(`window.term.clearSelection()`); + assert.equal(await page.evaluate(`window.callCount`), 2); + }); + + it('onRender', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.calls = []; + window.term.onRender(e => window.calls.push([e.start, e.end])); + `); + assert.deepEqual(await page.evaluate(`window.calls`), []); + await page.evaluate(`window.term.write('foo')`); + assert.deepEqual(await page.evaluate(`window.calls`), [[0, 0]]); + await page.evaluate(`window.term.write('bar\\n\\nbaz')`); + assert.deepEqual(await page.evaluate(`window.calls`), [[0, 0], [0, 2]]); + }); + + it('onResize', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.calls = []; + window.term.onResize(e => window.calls.push([e.cols, e.rows])); + `); + assert.deepEqual(await page.evaluate(`window.calls`), []); + await page.evaluate(`window.term.resize(10, 5)`); + assert.deepEqual(await page.evaluate(`window.calls`), [[10, 5]]); + await page.evaluate(`window.term.resize(20, 15)`); + assert.deepEqual(await page.evaluate(`window.calls`), [[10, 5], [20, 15]]); + }); + + it('onTitleChange', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.calls = []; + window.term.onTitleChange(e => window.calls.push(e)); + `); + assert.deepEqual(await page.evaluate(`window.calls`), []); + await page.evaluate(`window.term.write('\\x1b]2;foo\\x9c')`); + assert.deepEqual(await page.evaluate(`window.calls`), ['foo']); + }); + }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { From dfb5d8e663ec92b6935e519358fd2e609128112c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 23:53:38 -0700 Subject: [PATCH 49/97] Add focus, blur test --- src/public/Terminal.api.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 147519f4..380cc89c 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -101,6 +101,16 @@ describe('API Integration Tests', () => { assert.equal(await page.evaluate(`window.term.getSelection()`), ''); }); + it('focus, blur', async function(): Promise { + this.timeout(10000); + await openTerminal(); + assert.equal(await page.evaluate(`document.activeElement.className`), ''); + await page.evaluate(`window.term.focus()`); + assert.equal(await page.evaluate(`document.activeElement.className`), 'xterm-helper-textarea'); + await page.evaluate(`window.term.blur()`); + assert.equal(await page.evaluate(`document.activeElement.className`), ''); + }); + describe('Events', () => { it('onCursorMove', async function(): Promise { this.timeout(10000); From 740604f035faeb73339e882c0a5955bd661ee025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 11 May 2019 17:21:16 +0200 Subject: [PATCH 50/97] remove instanceof check --- src/renderer/BaseRenderLayer.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index ce50a651..498b24d0 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -11,7 +11,6 @@ import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/T import BaseCharAtlas from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../core/buffer/BufferLine'; -import { JoinedCellData } from './CharacterJoinerRegistry'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -264,9 +263,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { // skip cache right away if we draw in RGB // Note: to avoid bad runtime JoinedCellData will be skipped - // in the cache handler (atlasDidDraw == false) itself and + // in the cache handler itself (atlasDidDraw == false) and // fall through to uncached later down below - if (cell.isFgRGB() || cell.isBgRGB() || cell instanceof JoinedCellData) { + if (cell.isFgRGB() || cell.isBgRGB()) { this._drawUncachedChars(terminal, cell, x, y); return; } From eb6deb67f9273e11ac36108d1df2ab4d0387aed8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 10:10:31 -0700 Subject: [PATCH 51/97] Fix build issues --- src/Terminal.integration.ts | 1 - src/public/Terminal.ts | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index 459d84aa..5e12d2a3 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -13,7 +13,6 @@ import * as path from 'path'; import * as pty from 'node-pty'; import { assert } from 'chai'; import { Terminal } from './Terminal'; -import { Terminal as PublicTerminal } from './public/Terminal'; import { IViewport } from './Types'; import { CellData, WHITESPACE_CELL_CHAR } from './core/buffer/BufferLine'; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index eb3809b1..ee623ca3 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -4,7 +4,8 @@ */ import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm'; -import { ITerminal, IBufferLine, IBuffer } from '../Types'; +import { ITerminal, IBuffer } from '../Types'; +import { IBufferLine } from '../core/Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; import { IEvent } from '../common/EventEmitter2'; From bce2eae66f771ae4b10815b3872f3c2b89b2c8c2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 10:15:29 -0700 Subject: [PATCH 52/97] Update yarn.lock --- yarn.lock | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 440aa4f8..e14ad3a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -69,6 +69,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.108.tgz#852e8496bcfc5e74cae83a5eb3b30e5661e9b7b9" integrity sha512-5q14jNJCPW+Iwk6Y1JxtA7T5ov1aVRS2VA2PvRgFMZtCjoIo8WT1WO56dSV0MSiHR7BEoe2QNuXigBQNqbWdAw== +"@types/puppeteer@^1.12.4": + version "1.12.4" + resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.12.4.tgz#8388efdb0b30a54a7e7c4831ca0d709191d77ff1" + integrity sha512-aaGbJaJ9TuF9vZfTeoh876sBa+rYJWPwtsmHmYr28pGr42ewJnkDTq2aeSKEmS39SqUdkwLj73y/d7rBSp7mDQ== + dependencies: + "@types/node" "*" + "@types/tapable@*": version "1.0.4" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370" @@ -314,6 +321,13 @@ acorn@^5.6.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.2.tgz#91fa871883485d06708800318404e72bfb26dcc5" integrity sha512-cJrKCNcr2kv8dlDnbw+JPUGjHZzo4myaxOLmpOX8a+rgX94YeTcTMv/LFJUSByRpc+i4GgVnnhLxvMu/2Y+rqw== +agent-base@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" + integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== + dependencies: + es6-promisify "^5.0.0" + ajv-keywords@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" @@ -1307,7 +1321,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0, concat-stream@^1.6.1: +concat-stream@1.6.2, concat-stream@^1.5.0, concat-stream@^1.6.1: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -1606,7 +1620,7 @@ debug@2.6.8: dependencies: ms "2.0.0" -debug@2.X, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: +debug@2.6.9, debug@2.X, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -1620,6 +1634,13 @@ debug@^3.1.0: dependencies: ms "2.0.0" +debug@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + debug@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" @@ -1938,6 +1959,18 @@ es6-promise@^3.0.2, es6-promise@^3.1.2: resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" integrity sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM= +es6-promise@^4.0.3: + version "4.2.6" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" + integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + es6-symbol@^3.1.1, es6-symbol@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" @@ -2174,6 +2207,16 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extract-zip@^1.6.6: + version "1.6.7" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" + integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= + dependencies: + concat-stream "1.6.2" + debug "2.6.9" + mkdirp "0.5.1" + yauzl "2.4.1" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -2213,6 +2256,13 @@ fast-levenshtein@~2.0.4: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fd-slicer@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" + integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU= + dependencies: + pend "~1.2.0" + figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -3042,6 +3092,14 @@ https-browserify@~0.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" integrity sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI= +https-proxy-agent@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" + integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== + dependencies: + agent-base "^4.1.0" + debug "^3.1.0" + iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" @@ -4235,6 +4293,11 @@ mime@1.3.4: resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" integrity sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM= +mime@^2.0.3: + version "2.4.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.2.tgz#ce5229a5e99ffc313abac806b482c10e7ba6ac78" + integrity sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg== + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -4394,6 +4457,11 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= +ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + multipipe@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" @@ -5067,6 +5135,11 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -5165,6 +5238,11 @@ process@^0.11.10, process@~0.11.0: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= +progress@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -5178,6 +5256,11 @@ proxy-addr@~1.0.10: forwarded "~0.1.0" ipaddr.js "1.0.5" +proxy-from-env@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" + integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -5243,6 +5326,20 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +puppeteer@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-1.15.0.tgz#1680fac13e51f609143149a5b7fa99eec392b34f" + integrity sha512-D2y5kwA9SsYkNUmcBzu9WZ4V1SGHiQTmgvDZSx6sRYFsgV25IebL4V6FaHjF6MbwLK9C6f3G3pmck9qmwM8H3w== + dependencies: + debug "^4.1.0" + extract-zip "^1.6.6" + https-proxy-agent "^2.2.1" + mime "^2.0.3" + progress "^2.0.1" + proxy-from-env "^1.0.0" + rimraf "^2.6.1" + ws "^6.1.0" + qs@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/qs/-/qs-4.0.0.tgz#c31d9b74ec27df75e543a86c78728ed8d4623607" @@ -7100,6 +7197,13 @@ ws@^4.0.0: async-limiter "~1.0.0" safe-buffer "~5.1.0" +ws@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + xdg-basedir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2" @@ -7222,6 +7326,13 @@ yargs@~3.10.0: decamelize "^1.0.0" window-size "0.1.0" +yauzl@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" + integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU= + dependencies: + fd-slicer "~1.0.1" + zmodem.js@^0.1.5: version "0.1.7" resolved "https://registry.yarnpkg.com/zmodem.js/-/zmodem.js-0.1.7.tgz#247affb76d2b1e3042b3fc8b4a087b9d5db8d1ed" From d694eae6b5e4dd717aaa2fba0da998d4f0e41fbd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 10:32:53 -0700 Subject: [PATCH 53/97] Add some buffer tests --- src/public/Terminal.api.ts | 68 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 380cc89c..daa160d3 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -231,6 +231,74 @@ describe('API Integration Tests', () => { assert.deepEqual(await page.evaluate(`window.calls`), ['foo']); }); }); + + describe('buffer', () => { + it('cursorX, cursorY', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5, cols: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0); + assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 0); + await page.evaluate(`window.term.write('foo')`); + assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 3); + assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 0); + await page.evaluate(`window.term.write('\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 3); + assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); + await page.evaluate(`window.term.write('\\r')`); + assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0); + assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); + await page.evaluate(`window.term.write('abcde')`); + assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 5); + assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); + await page.evaluate(`window.term.write('\\n\\r\\n\\n\\n\\n\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0); + assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 4); + }); + + it('viewportY', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); + await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); + await page.evaluate(`window.term.write('\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 1); + await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 5); + await page.evaluate(`window.term.scrollLines(-1)`); + assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 4); + await page.evaluate(`window.term.scrollToTop()`); + assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); + }); + + it('baseY', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.baseY`), 0); + await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.baseY`), 0); + await page.evaluate(`window.term.write('\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.baseY`), 1); + await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); + await page.evaluate(`window.term.scrollLines(-1)`); + assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); + await page.evaluate(`window.term.scrollToTop()`); + assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); + }); + + it('length', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.length`), 5); + await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.length`), 5); + await page.evaluate(`window.term.write('\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.length`), 6); + await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.length`), 10); + }); + }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { From 46995fdbc1d5b308a04d746df5417d9e5b4e8c1d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 10:46:00 -0700 Subject: [PATCH 54/97] Add tests for buffer.getLine --- src/public/Terminal.api.ts | 45 ++++++++++++++++++++++++++++++++++++++ typings/xterm.d.ts | 7 +++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index daa160d3..6d777ee1 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -298,6 +298,51 @@ describe('API Integration Tests', () => { await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); assert.equal(await page.evaluate(`window.term.buffer.length`), 10); }); + + describe('getLine', () => { + it('isWrapped', async function(): Promise { + this.timeout(10000); + await openTerminal({ cols: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false); + assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), false); + await page.evaluate(`window.term.write('abcde')`); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false); + assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), false); + await page.evaluate(`window.term.write('f')`); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false); + assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), true); + }); + + it('translateToString', async function(): Promise { + this.timeout(10000); + await openTerminal({ cols: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), ' '); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), ''); + await page.evaluate(`window.term.write('foo')`); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), 'foo '); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); + await page.evaluate(`window.term.write('bar')`); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), 'fooba'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'fooba'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'r'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(false, 1)`), 'ooba'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(false, 1, 3)`), 'oo'); + }); + + it('getCell', async function(): Promise { + this.timeout(10000); + await openTerminal(); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).char`), ''); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).width`), 1); + await page.evaluate(`window.term.write('a文')`); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).char`), 'a'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).width`), 1); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(1).char`), '文'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(1).width`), 2); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(2).char`), ''); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(2).width`), 0); + }); + }); }); }); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 49bb6c31..00e7b215 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -916,7 +916,12 @@ declare module 'xterm' { getCell(x: number): IBufferCell; /** - * Gets the line + * Gets the line as a string. Note that this is gets only the string for the line, not taking + * isWrapped into account. + * + * @param trimRight Whether to trim any whitespace at the right of the line. + * @param startColumn The column to start from (inclusive). + * @param endColumn The column to end at (exclusive). */ translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string; } From 8a929ed1422f77a8b666bfdafb8a53efd89a1c97 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 11:02:15 -0700 Subject: [PATCH 55/97] Add API tests for loadAddon --- src/Terminal.ts | 1 + src/public/Terminal.api.ts | 46 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index 6b74dfbb..55c5c062 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -275,6 +275,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } public dispose(): void { + this._addonManager.dispose(); super.dispose(); if (this._windowsMode) { this._windowsMode.dispose(); diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 380cc89c..e56e3535 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -111,6 +111,52 @@ describe('API Integration Tests', () => { assert.equal(await page.evaluate(`document.activeElement.className`), ''); }); + describe('loadAddon', () => { + it('constructor', async function(): Promise { + this.timeout(10000); + await openTerminal({ cols: 5 }); + await page.evaluate(` + window.cols = 0; + window.term.loadAddon({ + activate: (t) => window.cols = t.cols, + dispose: () => {} + }); + `); + assert.equal(await page.evaluate(`window.cols`), 5); + }); + + it('dispose (addon)', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.disposeCalled = false + window.addon = { + activate: () => {}, + dispose: () => window.disposeCalled = true + }; + window.term.loadAddon(window.addon); + `); + assert.equal(await page.evaluate(`window.disposeCalled`), false); + await page.evaluate(`window.addon.dispose()`); + assert.equal(await page.evaluate(`window.disposeCalled`), true); + }); + + it('dispose (terminal)', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + window.disposeCalled = false + window.term.loadAddon({ + activate: () => {}, + dispose: () => window.disposeCalled = true + }); + `); + assert.equal(await page.evaluate(`window.disposeCalled`), false); + await page.evaluate(`window.term.dispose()`); + assert.equal(await page.evaluate(`window.disposeCalled`), true); + }); + }); + describe('Events', () => { it('onCursorMove', async function(): Promise { this.timeout(10000); From cd3f2609d6ecae53c1de4cb5835b759394b856b6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 12:12:53 -0700 Subject: [PATCH 56/97] Make tests use buffer API over _core --- src/public/Terminal.api.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 6d777ee1..935e1603 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -48,7 +48,7 @@ describe('API Integration Tests', () => { window.term.write('foo'); window.term.write('bar'); `); - assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(0, true)`), 'foobar'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar'); }); it('writeln', async function(): Promise { @@ -58,8 +58,8 @@ describe('API Integration Tests', () => { window.term.writeln('foo'); window.term.writeln('bar'); `); - assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(0, true)`), 'foo'); - assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(1, true)`), 'bar'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar'); }); it('clear', async function(): Promise { @@ -72,10 +72,10 @@ describe('API Integration Tests', () => { } `); await page.evaluate(`window.term.clear()`); - assert.equal(await page.evaluate(`window.term._core.buffer.lines.length`), '5'); - assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(0, true)`), 'test9'); + assert.equal(await page.evaluate(`window.term.buffer.length`), '5'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'test9'); for (let i = 1; i < 5; i++) { - assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(${i}, true)`), ''); + assert.equal(await page.evaluate(`window.term.buffer.getLine(${i}).translateToString(true)`), ''); } }); From 7a567d27177615f7abfad5ae47f6a88f1c1c9e1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 11 May 2019 23:10:18 +0200 Subject: [PATCH 57/97] apply time-based limit --- src/Terminal.ts | 81 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 2cb1dcf0..391cd991 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -183,6 +183,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // user input states public writeBuffer: string[]; + public writeBufferUtf8: Uint8Array[]; private _writeInProgress: boolean; /** @@ -340,6 +341,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // user input states this.writeBuffer = []; + this.writeBufferUtf8 = []; this._writeInProgress = false; this._xoffSentToCatchUp = false; @@ -1366,20 +1368,85 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } /** - * Writes utf8 data to the terminal. - * TODO: This currently does no flow control. + * Writes raw utf8 bytes to the terminal. + * @param data The text to write to the terminal. */ public writeUtf8(data: Uint8Array): void { + // Ensure the terminal isn't disposed if (this._isDisposed) { return; } - this._refreshStart = this.buffer.y; - this._refreshEnd = this.buffer.y; - this._inputHandler.parseUtf8(data); + // Ignore falsy data values (including the empty string) + if (!data) { + return; + } - this.updateRange(this.buffer.y); - this.refresh(this._refreshStart, this._refreshEnd); + this.writeBufferUtf8.push(data); + + // Send XOFF to pause the pty process if the write buffer becomes too large so + // xterm.js can catch up before more data is sent. This is necessary in order + // to keep signals such as ^C responsive. + if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBufferUtf8.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { + // XOFF - stop pty pipe + // XON will be triggered by emulator before processing data chunk + this.handler(C0.DC3); + this._xoffSentToCatchUp = true; + } + + if (!this._writeInProgress && this.writeBufferUtf8.length > 0) { + // Kick off a write which will write all data in sequence recursively + this._writeInProgress = true; + // Kick off an async innerWrite so more writes can come in while processing data + setTimeout(() => { + this._innerWriteUtf8(); + }); + } + } + + protected _innerWriteUtf8(bufferOffset: number = 0): void { + // Ensure the terminal isn't disposed + if (this._isDisposed) { + this.writeBufferUtf8 = []; + } + + const startTime = Date.now(); + while (this.writeBufferUtf8.length > bufferOffset) { + const data = this.writeBufferUtf8[bufferOffset]; + bufferOffset++; + + // If XOFF was sent in order to catch up with the pty process, resume it if + // we reached the end of the writeBuffer to allow more data to come in. + if (this._xoffSentToCatchUp && this.writeBufferUtf8.length === bufferOffset) { + this.handler(C0.DC1); + this._xoffSentToCatchUp = false; + } + + this._refreshStart = this.buffer.y; + this._refreshEnd = this.buffer.y; + + // HACK: Set the parser state based on it's state at the time of return. + // This works around the bug #662 which saw the parser state reset in the + // middle of parsing escape sequence in two chunks. For some reason the + // state of the parser resets to 0 after exiting parser.parse. This change + // just sets the state back based on the correct return statement. + + this._inputHandler.parseUtf8(data); + + this.updateRange(this.buffer.y); + this.refresh(this._refreshStart, this._refreshEnd); + + if (Date.now() - startTime >= WRITE_TIMEOUT_MS) { + break; + } + } + if (this.writeBufferUtf8.length > bufferOffset) { + // Allow renderer to catch up before processing the next batch + setTimeout(() => this._innerWriteUtf8(bufferOffset), 0); + } else { + this._writeInProgress = false; + this.writeBufferUtf8 = []; + } } /** From ce079f399c89ba059797b582a8045686a0a0573f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 12 May 2019 00:07:07 +0200 Subject: [PATCH 58/97] fix docstring --- src/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 391cd991..383b73f8 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1369,7 +1369,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Writes raw utf8 bytes to the terminal. - * @param data The text to write to the terminal. + * @param data UintArray with UTF8 bytes to write to the terminal. */ public writeUtf8(data: Uint8Array): void { // Ensure the terminal isn't disposed From f41e00ed97f6798dbc957bdcb9c3c049c3b56166 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 20:46:55 -0700 Subject: [PATCH 59/97] Return undefined if cell or line doesn't exist --- src/public/Terminal.ts | 9 +++++++-- typings/xterm.d.ts | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index ee623ca3..dd07c735 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -180,14 +180,19 @@ class BufferApiView implements IBufferApi { public get viewportY(): number { return this._buffer.ydisp; } public get baseY(): number { return this._buffer.ybase; } public get length(): number { return this._buffer.lines.length; } - public getLine(y: number): IBufferLineApi { return new BufferLineApiView(this._buffer.lines.get(y)); } + public getLine(y: number): IBufferLineApi | undefined { return new BufferLineApiView(this._buffer.lines.get(y)); } } class BufferLineApiView implements IBufferLineApi { constructor(private _line: IBufferLine) {} public get isWrapped(): boolean { return this._line.isWrapped; } - public getCell(x: number): IBufferCellApi { return new BufferCellApiView(this._line, x); } + public getCell(x: number): IBufferCellApi | undefined { + if (x < 0 && x >= this._line.length) { + return undefined; + } + return new BufferCellApiView(this._line, x); + } public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string { return this._line.translateToString(trimRight, startColumn, endColumn); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 00e7b215..e9643b63 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -895,11 +895,11 @@ declare module 'xterm' { readonly length: number; /** - * Gets a line from the buffer. + * Gets a line from the buffer, or undefined if the line index does not exist. * * @param y The line index to get. */ - getLine(y: number): IBufferLine; + getLine(y: number): IBufferLine | undefined; } interface IBufferLine { @@ -909,7 +909,7 @@ declare module 'xterm' { readonly isWrapped: boolean; /** - * Gets a cell from the line. + * Gets a cell from the line, or undefined if the line index does not exist. * * @param x The character index to get. */ From 49574be510ce7ac5a9b02e1dc96cbff80f710504 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 20:49:48 -0700 Subject: [PATCH 60/97] Add warnings to getCell and getLine --- typings/xterm.d.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index e9643b63..0714f55f 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -356,9 +356,9 @@ declare module 'xterm' { readonly cols: number; /** - * (EXPERIMENTAL) The terminal's current buffer, note that this might be - * either the normal buffer or the alt buffer depending on what's running in - * the terminal. + * (EXPERIMENTAL) The terminal's current buffer, this might be either the + * normal buffer or the alt buffer depending on what's running in the + * terminal. */ readonly buffer: IBuffer; @@ -897,6 +897,9 @@ declare module 'xterm' { /** * Gets a line from the buffer, or undefined if the line index does not exist. * + * Note that the result of this function should be used immediately after calling as when the + * terminal updates it could lead to unexpected behavior. + * * @param y The line index to get. */ getLine(y: number): IBufferLine | undefined; @@ -911,6 +914,9 @@ declare module 'xterm' { /** * Gets a cell from the line, or undefined if the line index does not exist. * + * Note that the result of this function should be used immediately after calling as when the + * terminal updates it could lead to unexpected behavior. + * * @param x The character index to get. */ getCell(x: number): IBufferCell; From 70ba349a858d2fcb810e60db53a5ba142b8f365c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 20:58:51 -0700 Subject: [PATCH 61/97] Fix invalid index check, add api tests --- src/public/Terminal.api.ts | 11 ++++++++++- src/public/Terminal.ts | 10 ++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 935e1603..031e03d5 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -300,6 +300,13 @@ describe('API Integration Tests', () => { }); describe('getLine', () => { + it('invalid index', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.getLine(-1)`), undefined); + assert.equal(await page.evaluate(`window.term.buffer.getLine(5)`), undefined); + }); + it('isWrapped', async function(): Promise { this.timeout(10000); await openTerminal({ cols: 5 }); @@ -331,7 +338,9 @@ describe('API Integration Tests', () => { it('getCell', async function(): Promise { this.timeout(10000); - await openTerminal(); + await openTerminal({ cols: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(-1)`), undefined); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(5)`), undefined); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).char`), ''); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).width`), 1); await page.evaluate(`window.term.write('a文')`); diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index dd07c735..0c6a7745 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -180,7 +180,13 @@ class BufferApiView implements IBufferApi { public get viewportY(): number { return this._buffer.ydisp; } public get baseY(): number { return this._buffer.ybase; } public get length(): number { return this._buffer.lines.length; } - public getLine(y: number): IBufferLineApi | undefined { return new BufferLineApiView(this._buffer.lines.get(y)); } + public getLine(y: number): IBufferLineApi | undefined { + const line = this._buffer.lines.get(y); + if (!line) { + return undefined; + } + return new BufferLineApiView(line); + } } class BufferLineApiView implements IBufferLineApi { @@ -188,7 +194,7 @@ class BufferLineApiView implements IBufferLineApi { public get isWrapped(): boolean { return this._line.isWrapped; } public getCell(x: number): IBufferCellApi | undefined { - if (x < 0 && x >= this._line.length) { + if (x < 0 || x >= this._line.length) { return undefined; } return new BufferCellApiView(this._line, x); From 2340fcd071975ced2ed7451b89773ff483a07b44 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 21:21:36 -0700 Subject: [PATCH 62/97] Fix compile and update yarn.lock with utf8 --- src/Types.ts | 1 + yarn.lock | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/Types.ts b/src/Types.ts index b6b8ac86..84927a65 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -263,6 +263,7 @@ export interface IPublicTerminal extends IDisposable, IEventEmitter { scrollToLine(line: number): void; clear(): void; write(data: string): void; + writeUtf8(data: Uint8Array): void; getOption(key: string): any; setOption(key: string, value: any): void; refresh(start: number, end: number): void; diff --git a/yarn.lock b/yarn.lock index e14ad3a1..87ca4a18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -93,6 +93,11 @@ dependencies: source-map "^0.6.1" +"@types/utf8@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@types/utf8/-/utf8-2.1.6.tgz#430cabb71a42d0a3613cce5621324fe4f5a25753" + integrity sha512-pRs2gYF5yoKYrgSaira0DJqVg2tFuF+Qjp838xS7K+mJyY2jJzjsrl6y17GbIa4uMRogMbxs+ghNCvKg6XyNrA== + "@types/webpack@^4.4.11": version "4.4.11" resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.4.11.tgz#0ca832870d55c4e92498c01d22d00d02b0f62ae9" @@ -6823,6 +6828,11 @@ user-home@^1.1.1: resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" integrity sha1-K1viOjK2Onyd640PKNSFcko98ZA= +utf8@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" + integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== + util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" From 00423407c189a8e7e9e6e2a4736d6b579442ddff Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 22:08:25 -0700 Subject: [PATCH 63/97] Clean up --- src/core/input/TextDecoder.test.ts | 3 --- src/core/input/TextDecoder.ts | 4 ---- typings/xterm.d.ts | 14 +++++++------- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/core/input/TextDecoder.test.ts b/src/core/input/TextDecoder.test.ts index 68eb1d9b..dc358bc5 100644 --- a/src/core/input/TextDecoder.test.ts +++ b/src/core/input/TextDecoder.test.ts @@ -28,7 +28,6 @@ function fromByteString(s: string): Uint8Array { return result; } - const TEST_STRINGS = [ 'Лорем ипсум долор сит амет, ех сеа аццусам диссентиет. Ан еос стет еирмод витуперата. Иус дицерет урбанитас ет. Ан при алтера долорес сплендиде, цу яуо интегре денияуе, игнота волуптариа инструцтиор цу вим.', 'ლორემ იფსუმ დოლორ სით ამეთ, ფაცერ მუციუს ცონსეთეთურ ყუო იდ, ფერ ვივენდუმ ყუაერენდუმ ეა, ესთ ამეთ მოვეთ სუავითათე ცუ. ვითაე სენსიბუს ან ვიხ. ეხერცი დეთერრუისსეთ უთ ყუი. ვოცენთ დებითის ადიფისცი ეთ ფერ. ნეც ან ფეუგაით ფორენსიბუს ინთერესსეთ. იდ დიცო რიდენს იუს. დისსენთიეთ ცონსეყუუნთურ სედ ნე, ნოვუმ მუნერე ეუმ ათ, ნე ეუმ ნიჰილ ირაცუნდია ურბანითას.', @@ -41,7 +40,6 @@ const TEST_STRINGS = [ 'Лорем ლორემ अधिकांश 覧六子 八メル 모든 בקרבת 💮 😂 äggg 123€ 𝄞.' ]; - describe('text encodings', () => { it('stringFromCodePoint/utf32ToString', () => { const s = 'abcdefg'; @@ -110,7 +108,6 @@ describe('text encodings', () => { assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€'); }); }); - }); describe('Utf8ToUtf32 decoder', () => { diff --git a/src/core/input/TextDecoder.ts b/src/core/input/TextDecoder.ts index b9ac19ce..75029822 100644 --- a/src/core/input/TextDecoder.ts +++ b/src/core/input/TextDecoder.ts @@ -3,7 +3,6 @@ * @license MIT */ - /** * Polyfill - Convert UTF32 codepoint into JS string. * Note: The built-in String.fromCodePoint happens to be much slower @@ -19,7 +18,6 @@ export function stringFromCodePoint(codePoint: number): string { return String.fromCharCode(codePoint); } - /** * Convert UTF32 char codes into JS string. * Basically the same as `stringFromCodePoint` but for multiple codepoints @@ -44,7 +42,6 @@ export function utf32ToString(data: Uint32Array, start: number = 0, end: number return result; } - /** * StringToUtf32 - decodes UTF16 sequences into UTF32 codepoints. * To keep the decoder in line with JS strings it handles single surrogates as UCS2. @@ -211,7 +208,6 @@ export class Utf8ToUtf32 { const fourStop = length - 4; let i = startPos; while (i < length) { - /** * ASCII shortcut with loop unrolled to 4 consecutive ASCII chars. * This is a compromise between speed gain for ASCII diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 2edad79a..b3bb582e 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -553,12 +553,6 @@ declare module 'xterm' { */ resize(columns: number, rows: number): void; - /** - * Writes text to the terminal, followed by a break line character (\n). - * @param data The text to write to the terminal. - */ - writeln(data: string): void; - /** * Opens the terminal within an element. * @param parent The element to create the terminal within. This element @@ -746,7 +740,13 @@ declare module 'xterm' { write(data: string): void; /** - * Writes UTF8 data to the terminal. + * Writes text to the terminal, followed by a break line character (\n). + * @param data The text to write to the terminal. + */ + writeln(data: string): void; + + /** + * Writes text to the terminal encoded as UTF-8 to the terminal. * @param data The data to write to the terminal. */ writeUtf8(data: Uint8Array): void; From e5dfc5603040feb469d8ca801551b37b01b1cf7a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 22:24:33 -0700 Subject: [PATCH 64/97] Add api test for writeUtf8 --- src/public/Terminal.api.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 031e03d5..57645cf1 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -62,6 +62,20 @@ describe('API Integration Tests', () => { assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar'); }); + it.only('writeUtf8', async function(): Promise { + this.timeout(10000); + await openTerminal(); + await page.evaluate(` + // foo + window.term.writeUtf8(new Uint8Array([102, 111, 111])); + // bar + window.term.writeUtf8(new Uint8Array([98, 97, 114])); + // 文 + window.term.writeUtf8(new Uint8Array([230, 150, 135])); + `); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文'); + }); + it('clear', async function(): Promise { this.timeout(10000); await openTerminal({ rows: 5 }); From c13d6e5eeb6287f78962dce20a24dbb966eaec02 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 22:29:01 -0700 Subject: [PATCH 65/97] Make write and writeln consistent with writeUtf8 test --- src/public/Terminal.api.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 57645cf1..5d124532 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -47,8 +47,9 @@ describe('API Integration Tests', () => { await page.evaluate(` window.term.write('foo'); window.term.write('bar'); + window.term.write('文'); `); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文'); }); it('writeln', async function(): Promise { @@ -57,12 +58,14 @@ describe('API Integration Tests', () => { await page.evaluate(` window.term.writeln('foo'); window.term.writeln('bar'); + window.term.writeln('文'); `); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(2).translateToString(true)`), '文'); }); - it.only('writeUtf8', async function(): Promise { + it('writeUtf8', async function(): Promise { this.timeout(10000); await openTerminal(); await page.evaluate(` From 780e924b213373e23f03720225e99c8017695d03 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 23:15:04 -0700 Subject: [PATCH 66/97] Use stable API for buffer in search addon --- src/addons/search/Interfaces.ts | 1 - src/addons/search/SearchHelper.ts | 34 +++++++++++++++---------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/addons/search/Interfaces.ts b/src/addons/search/Interfaces.ts index a1f05895..d99bd271 100644 --- a/src/addons/search/Interfaces.ts +++ b/src/addons/search/Interfaces.ts @@ -7,7 +7,6 @@ import { Terminal } from 'xterm'; // TODO: Don't rely on this private API export interface ITerminalCore { - buffer: any; selectionManager: any; } diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 7db1ed43..ce3d0004 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -45,7 +45,7 @@ export class SearchHelper implements ISearchHelper { } let startCol: number = 0; - let startRow = this._terminal._core.buffer.ydisp; + let startRow = this._terminal.buffer.viewportY; if (selectionManager.selectionEnd) { // Start from the selection end if there is a selection @@ -64,7 +64,7 @@ export class SearchHelper implements ISearchHelper { let cumulativeCols = startCol; // If startRow is wrapped row, scan for unwrapped row above. // So we can start matching on wrapped line from long unwrapped line. - while (this._terminal._core.buffer.lines.get(findingRow).isWrapped) { + while (this._terminal.buffer.getLine(findingRow).isWrapped) { findingRow--; cumulativeCols += this._terminal.cols; } @@ -75,7 +75,7 @@ export class SearchHelper implements ISearchHelper { // Search from startRow + 1 to end if (!result) { - for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) { + for (let y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) { // If the current line is wrapped line, increase index of column to ignore the previous scan // Otherwise, reset beginning column index to zero with set new unwrapped line index @@ -118,7 +118,7 @@ export class SearchHelper implements ISearchHelper { } const isReverseSearch = true; - let startRow = this._terminal._core.buffer.ydisp + this._terminal.rows - 1; + let startRow = this._terminal.buffer.viewportY + this._terminal.rows - 1; let startCol = this._terminal.cols; if (selectionManager.selectionStart) { @@ -139,7 +139,7 @@ export class SearchHelper implements ISearchHelper { // If the line is wrapped line, increase number of columns that is needed to be scanned // Se we can scan on wrapped line from unwrapped line let cumulativeCols = this._terminal.cols; - if (this._terminal._core.buffer.lines.get(startRow).isWrapped) { + if (this._terminal.buffer.getLine(startRow).isWrapped) { cumulativeCols += startCol; } for (let y = startRow - 1; y >= 0; y--) { @@ -149,7 +149,7 @@ export class SearchHelper implements ISearchHelper { } // If the current line is wrapped line, increase scanning range, // preparing for scanning on unwrapped line - if (this._terminal._core.buffer.lines.get(y).isWrapped) { + if (this._terminal.buffer.getLine(y).isWrapped) { cumulativeCols += this._terminal.cols; } else { cumulativeCols = this._terminal.cols; @@ -160,14 +160,14 @@ export class SearchHelper implements ISearchHelper { // Search from the bottom to startRow (search the whole startRow again in // case startCol > 0) if (!result) { - const searchFrom = this._terminal._core.buffer.ybase + this._terminal.rows - 1; + const searchFrom = this._terminal.buffer.baseY + this._terminal.rows - 1; let cumulativeCols = this._terminal.cols; for (let y = searchFrom; y >= startRow; y--) { result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch); if (result) { break; } - if (this._terminal._core.buffer.lines.get(y).isWrapped) { + if (this._terminal.buffer.getLine(y).isWrapped) { cumulativeCols += this._terminal.cols; } else { cumulativeCols = this._terminal.cols; @@ -184,7 +184,7 @@ export class SearchHelper implements ISearchHelper { */ private _initLinesCache(): void { if (!this._linesCache) { - this._linesCache = new Array(this._terminal._core.buffer.length); + this._linesCache = new Array(this._terminal.buffer.length); this._cursorMoveListener = this._terminal.onCursorMove(() => this._destroyLinesCache()); this._resizeListener = this._terminal.onResize(() => this._destroyLinesCache()); } @@ -234,7 +234,7 @@ export class SearchHelper implements ISearchHelper { protected _findInLine(term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult { // Ignore wrapped lines, only consider on unwrapped line (first row of command string). - if (this._terminal._core.buffer.lines.get(row).isWrapped) { + if (this._terminal.buffer.getLine(row).isWrapped) { return; } let stringLine = this._linesCache ? this._linesCache[row] : void 0; @@ -286,18 +286,18 @@ export class SearchHelper implements ISearchHelper { return; } - const line = this._terminal._core.buffer.lines.get(row); + const line = this._terminal.buffer.getLine(row); for (let i = 0; i < resultIndex; i++) { - const charData = line.get(i); + const cell = line.getCell(i); // Adjust the searchIndex to normalize emoji into single chars - const char = charData[1/*CHAR_DATA_CHAR_INDEX*/]; + const char = cell.char; if (char.length > 1) { resultIndex -= char.length - 1; } // Adjust the searchIndex for empty characters following wide unicode // chars (eg. CJK) - const charWidth = charData[2/*CHAR_DATA_WIDTH_INDEX*/]; + const charWidth = cell.width; if (charWidth === 0) { resultIndex++; } @@ -322,9 +322,9 @@ export class SearchHelper implements ISearchHelper { let lineWrapsToNext: boolean; do { - const nextLine = this._terminal._core.buffer.lines.get(lineIndex + 1); + const nextLine = this._terminal.buffer.getLine(lineIndex + 1); lineWrapsToNext = nextLine ? nextLine.isWrapped : false; - lineString += this._terminal._core.buffer.translateBufferLineToString(lineIndex, !lineWrapsToNext && trimRight).substring(0, this._terminal.cols); + lineString += this._terminal.buffer.getLine(lineIndex).translateToString(!lineWrapsToNext && trimRight).substring(0, this._terminal.cols); lineIndex++; } while (lineWrapsToNext); @@ -342,7 +342,7 @@ export class SearchHelper implements ISearchHelper { return false; } this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length); - this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp); + this._terminal.scrollLines(result.row - this._terminal.buffer.viewportY); return true; } } From 161c7f9339dd714bb7b855b5c84f49d7a9e67eed Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 23:22:46 -0700 Subject: [PATCH 67/97] Use API clearSelection in search addon --- src/addons/search/SearchHelper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index ce3d0004..7f24a618 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -40,7 +40,7 @@ export class SearchHelper implements ISearchHelper { let result: ISearchResult; if (!term || term.length === 0) { - selectionManager.clearSelection(); + this._terminal.clearSelection(); return false; } @@ -113,7 +113,7 @@ export class SearchHelper implements ISearchHelper { let result: ISearchResult; if (!term || term.length === 0) { - selectionManager.clearSelection(); + this._terminal.clearSelection(); return false; } From 6674f05cc8eac07bde01c9a3185a475de835407c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 23:35:31 -0700 Subject: [PATCH 68/97] Implement setSelection API Fixes #1443 --- src/Terminal.ts | 10 ++++++++++ src/TestUtils.test.ts | 3 +++ src/Types.ts | 1 + src/public/Terminal.ts | 3 +++ typings/xterm.d.ts | 8 ++++++++ 5 files changed, 25 insertions(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index 6da60458..56205d1e 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1535,6 +1535,16 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II return this.selectionManager ? this.selectionManager.hasSelection : false; } + /** + * Selects text within the terminal. + * @param column The column the selection starts at.. + * @param row The row the selection starts at. + * @param length The length of the selection. + */ + public setSelection(column: number, row: number, length: number): void { + this.selectionManager.setSelection(column, row, length); + } + /** * Gets the terminal's current selection, this is useful for implementing copy * behavior outside of xterm.js. diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 1d5008c8..f5008a43 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -80,6 +80,9 @@ export class MockTerminal implements ITerminal { hasSelection(): boolean { throw new Error('Method not implemented.'); } + setSelection(column: number, row: number, length: number): void { + throw new Error('Method not implemented.'); + } getSelection(): string { throw new Error('Method not implemented.'); } diff --git a/src/Types.ts b/src/Types.ts index 5841626b..238dbd41 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -249,6 +249,7 @@ export interface IPublicTerminal extends IDisposable, IEventEmitter { deregisterCharacterJoiner(joinerId: number): void; addMarker(cursorYOffset: number): IMarker; hasSelection(): boolean; + setSelection(column: number, row: number, length: number): void; getSelection(): string; clearSelection(): void; selectAll(): void; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 0c6a7745..3f1cfd0c 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -96,6 +96,9 @@ export class Terminal implements ITerminalApi { public hasSelection(): boolean { return this._core.hasSelection(); } + public setSelection(column: number, row: number, length: number): void { + this._core.setSelection(column, row, length); + } public getSelection(): string { return this._core.getSelection(); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0714f55f..59eebc14 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -670,6 +670,14 @@ declare module 'xterm' { */ hasSelection(): boolean; + /** + * Selects text within the terminal. + * @param column The column the selection starts at.. + * @param row The row the selection starts at. + * @param length The length of the selection. + */ + setSelection(column: number, row: number, length: number): void; + /** * Gets the terminal's current selection, this is useful for implementing * copy behavior outside of xterm.js. From da7c39e5267cf4f198638b68713576ad57492d2c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 23:37:42 -0700 Subject: [PATCH 69/97] Add API test --- src/public/Terminal.api.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 031e03d5..772ffdc3 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -99,6 +99,9 @@ describe('API Integration Tests', () => { await page.evaluate(`window.term.clearSelection()`); assert.equal(await page.evaluate(`window.term.hasSelection()`), false); assert.equal(await page.evaluate(`window.term.getSelection()`), ''); + await page.evaluate(`window.term.setSelection(1, 2, 2)`) + assert.equal(await page.evaluate(`window.term.hasSelection()`), true); + assert.equal(await page.evaluate(`window.term.getSelection()`), 'oo'); }); it('focus, blur', async function(): Promise { From 5bbbbf7348e4f0e65afb271912a7def198769404 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 23:52:38 -0700 Subject: [PATCH 70/97] Expose getSelectionPosition and select APIs instead --- src/Terminal.ts | 17 ++++++++++-- src/TestUtils.test.ts | 11 +++++--- src/Types.ts | 5 ++-- src/addons/search/SearchHelper.ts | 2 +- src/public/Terminal.api.ts | 2 +- src/public/Terminal.ts | 9 ++++-- typings/xterm.d.ts | 46 +++++++++++++++++++++++++------ 7 files changed, 71 insertions(+), 21 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 56205d1e..58e58b6e 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -43,7 +43,7 @@ import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager'; import { MouseZoneManager } from './MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './ui/ScreenDprMonitor'; -import { ITheme, IMarker, IDisposable } from 'xterm'; +import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; @@ -1541,7 +1541,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * @param row The row the selection starts at. * @param length The length of the selection. */ - public setSelection(column: number, row: number, length: number): void { + public select(column: number, row: number, length: number): void { this.selectionManager.setSelection(column, row, length); } @@ -1553,6 +1553,19 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II return this.selectionManager ? this.selectionManager.selectionText : ''; } + public getSelectionPosition(): ISelectionPosition | undefined { + if (!this.selectionManager.hasSelection) { + return undefined; + } + + return { + startColumn: this.selectionManager.selectionStart[0], + startRow: this.selectionManager.selectionStart[1], + endColumn: this.selectionManager.selectionEnd[0], + endRow: this.selectionManager.selectionEnd[1] + }; + } + /** * Clears the current terminal selection. */ diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index f5008a43..3f53c7d3 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -9,7 +9,7 @@ import { IBufferLine, ICellData, IAttributeData } from './core/Types'; import { ICircularList, XtermListener } from './common/Types'; import { Buffer } from './Buffer'; import * as Browser from './common/Platform'; -import { ITheme, IDisposable, IMarker, IEvent } from 'xterm'; +import { ITheme, IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; import { AttributeData } from './core/buffer/BufferLine'; @@ -80,15 +80,18 @@ export class MockTerminal implements ITerminal { hasSelection(): boolean { throw new Error('Method not implemented.'); } - setSelection(column: number, row: number, length: number): void { - throw new Error('Method not implemented.'); - } getSelection(): string { throw new Error('Method not implemented.'); } + getSelectionPosition(): ISelectionPosition | undefined { + throw new Error('Method not implemented.'); + } clearSelection(): void { throw new Error('Method not implemented.'); } + select(column: number, row: number, length: number): void { + throw new Error('Method not implemented.'); + } selectAll(): void { throw new Error('Method not implemented.'); } diff --git a/src/Types.ts b/src/Types.ts index 238dbd41..26a35814 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker } from 'xterm'; +import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker, ISelectionPosition } from 'xterm'; import { IColorSet, IRenderer } from './renderer/Types'; import { ICharset, IAttributeData, ICellData, IBufferLine, CharData } from './core/Types'; import { ICircularList } from './common/Types'; @@ -249,9 +249,10 @@ export interface IPublicTerminal extends IDisposable, IEventEmitter { deregisterCharacterJoiner(joinerId: number): void; addMarker(cursorYOffset: number): IMarker; hasSelection(): boolean; - setSelection(column: number, row: number, length: number): void; getSelection(): string; + getSelectionPosition(): ISelectionPosition | undefined; clearSelection(): void; + select(column: number, row: number, length: number): void; selectAll(): void; selectLines(start: number, end: number): void; dispose(): void; diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 7db1ed43..3dc99603 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -341,7 +341,7 @@ export class SearchHelper implements ISearchHelper { this._terminal.clearSelection(); return false; } - this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length); + this._terminal.select(result.col, result.row, result.term.length); this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp); return true; } diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 772ffdc3..3f289f84 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -99,7 +99,7 @@ describe('API Integration Tests', () => { await page.evaluate(`window.term.clearSelection()`); assert.equal(await page.evaluate(`window.term.hasSelection()`), false); assert.equal(await page.evaluate(`window.term.getSelection()`), ''); - await page.evaluate(`window.term.setSelection(1, 2, 2)`) + await page.evaluate(`window.term.select(1, 2, 2)`) assert.equal(await page.evaluate(`window.term.hasSelection()`), true); assert.equal(await page.evaluate(`window.term.getSelection()`), 'oo'); }); diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 3f1cfd0c..96406919 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, ISelectionPosition } from 'xterm'; import { ITerminal, IBuffer } from '../Types'; import { IBufferLine } from '../core/Types'; import { Terminal as TerminalCore } from '../Terminal'; @@ -96,12 +96,15 @@ export class Terminal implements ITerminalApi { public hasSelection(): boolean { return this._core.hasSelection(); } - public setSelection(column: number, row: number, length: number): void { - this._core.setSelection(column, row, length); + public select(column: number, row: number, length: number): void { + this._core.select(column, row, length); } public getSelection(): string { return this._core.getSelection(); } + public getSelectionPosition(): ISelectionPosition | undefined { + return this._core.getSelectionPosition(); + } public clearSelection(): void { this._core.clearSelection(); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 59eebc14..466ddb6d 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -670,25 +670,30 @@ declare module 'xterm' { */ hasSelection(): boolean; - /** - * Selects text within the terminal. - * @param column The column the selection starts at.. - * @param row The row the selection starts at. - * @param length The length of the selection. - */ - setSelection(column: number, row: number, length: number): void; - /** * Gets the terminal's current selection, this is useful for implementing * copy behavior outside of xterm.js. */ getSelection(): string; + /** + * Gets the selection position or undefined if there is no selection. + */ + getSelectionPosition(): ISelectionPosition | undefined; + /** * Clears the current terminal selection. */ clearSelection(): void; + /** + * Selects text within the terminal. + * @param column The column the selection starts at.. + * @param row The row the selection starts at. + * @param length The length of the selection. + */ + select(column: number, row: number, length: number): void; + /** * Selects all text within the terminal. */ @@ -872,6 +877,31 @@ declare module 'xterm' { static applyAddon(addon: any): void; } + /** + * An object representing a selecrtion within the terminal. + */ + interface ISelectionPosition { + /** + * The start column of the selection. + */ + startColumn: number; + + /** + * The start row of the selection. + */ + startRow: number; + + /** + * The end column of the selection. + */ + endColumn: number; + + /** + * The end row of the selection. + */ + endRow: number; + } + interface IBuffer { /** * The y position of the cursor. This ranges between `0` (when the From 9ca9f7296efdedfc0db20a57007749a69609c29d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 23:56:34 -0700 Subject: [PATCH 71/97] Use new APIs in selection addon --- src/addons/search/Interfaces.ts | 1 - src/addons/search/SearchHelper.ts | 24 ++++++++++-------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/addons/search/Interfaces.ts b/src/addons/search/Interfaces.ts index a1f05895..27e3c4b3 100644 --- a/src/addons/search/Interfaces.ts +++ b/src/addons/search/Interfaces.ts @@ -8,7 +8,6 @@ import { Terminal } from 'xterm'; // TODO: Don't rely on this private API export interface ITerminalCore { buffer: any; - selectionManager: any; } export interface ISearchAddonTerminal extends Terminal { diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 3dc99603..c42167fa 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -35,25 +35,23 @@ export class SearchHelper implements ISearchHelper { * @return Whether a result was found. */ public findNext(term: string, searchOptions?: ISearchOptions): boolean { - const selectionManager = this._terminal._core.selectionManager; const {incremental} = searchOptions; let result: ISearchResult; if (!term || term.length === 0) { - selectionManager.clearSelection(); + this._terminal.clearSelection(); return false; } let startCol: number = 0; let startRow = this._terminal._core.buffer.ydisp; - if (selectionManager.selectionEnd) { + if (this._terminal.hasSelection()) { // Start from the selection end if there is a selection // For incremental search, use existing row - if (this._terminal.getSelection().length !== 0) { - startRow = incremental ? selectionManager.selectionStart[1] : selectionManager.selectionEnd[1]; - startCol = incremental ? selectionManager.selectionStart[0] : selectionManager.selectionEnd[0]; - } + const currentSelection = this._terminal.getSelectionPosition(); + startRow = incremental ? currentSelection.startRow : currentSelection.endRow; + startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn; } this._initLinesCache(); @@ -109,11 +107,10 @@ export class SearchHelper implements ISearchHelper { * @return Whether a result was found. */ public findPrevious(term: string, searchOptions?: ISearchOptions): boolean { - const selectionManager = this._terminal._core.selectionManager; let result: ISearchResult; if (!term || term.length === 0) { - selectionManager.clearSelection(); + this._terminal.clearSelection(); return false; } @@ -121,12 +118,11 @@ export class SearchHelper implements ISearchHelper { let startRow = this._terminal._core.buffer.ydisp + this._terminal.rows - 1; let startCol = this._terminal.cols; - if (selectionManager.selectionStart) { + if (this._terminal.hasSelection()) { // Start from the selection start if there is a selection - if (this._terminal.getSelection().length !== 0) { - startRow = selectionManager.selectionStart[1]; - startCol = selectionManager.selectionStart[0]; - } + const currentSelection = this._terminal.getSelectionPosition(); + startRow = currentSelection.startRow; + startCol = currentSelection.startColumn; } this._initLinesCache(); From a7fa58cc60073ef2ab23e3274d5e32eba5fea43b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 23:59:51 -0700 Subject: [PATCH 72/97] Add API test for getSelectionPosition --- src/public/Terminal.api.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 3f289f84..a16f2c62 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -89,19 +89,23 @@ describe('API Integration Tests', () => { it('selection', async function(): Promise { this.timeout(10000); - await openTerminal({ rows: 5 }); + await openTerminal({ rows: 5, cols: 5 }); await page.evaluate(`window.term.write('\\n\\nfoo\\n\\n\\rbar\\n\\n\\rbaz')`); assert.equal(await page.evaluate(`window.term.hasSelection()`), false); assert.equal(await page.evaluate(`window.term.getSelection()`), ''); + assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), undefined); await page.evaluate(`window.term.selectAll()`); assert.equal(await page.evaluate(`window.term.hasSelection()`), true); assert.equal(await page.evaluate(`window.term.getSelection()`), '\n\nfoo\n\nbar\n\nbaz'); + assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { startColumn: 0, startRow: 0, endColumn: 5, endRow: 6 }); await page.evaluate(`window.term.clearSelection()`); assert.equal(await page.evaluate(`window.term.hasSelection()`), false); assert.equal(await page.evaluate(`window.term.getSelection()`), ''); - await page.evaluate(`window.term.select(1, 2, 2)`) + assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), undefined); + await page.evaluate(`window.term.select(1, 2, 2)`); assert.equal(await page.evaluate(`window.term.hasSelection()`), true); assert.equal(await page.evaluate(`window.term.getSelection()`), 'oo'); + assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { startColumn: 1, startRow: 2, endColumn: 3, endRow: 2 }); }); it('focus, blur', async function(): Promise { From f654044e5eb0a6dafe7452b99065c613b3b45662 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 12 May 2019 00:17:59 -0700 Subject: [PATCH 73/97] Fix search addon unit tests --- src/addons/search/search.test.ts | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/addons/search/search.test.ts b/src/addons/search/search.test.ts index 6551fa8b..aee88f50 100644 --- a/src/addons/search/search.test.ts +++ b/src/addons/search/search.test.ts @@ -23,11 +23,56 @@ class MockTerminal { get core(): any { return this._core; } + get buffer(): IBuffer { + // TODO: This is a hacky workaround until we use puppeteer for addon tests + const buffer = this._core.buffer; + return { + cursorY: buffer.y, + cursorX: buffer.x, + viewportY: buffer.ydisp, + baseY: buffer.ybase, + length: buffer.length, + getLine(y: number): IBufferLine { + return { + isWrapped: buffer.lines.get(y) ? buffer.lines.get(y).isWrapped : false, + getCell(x: number): IBufferCell { + return { + char: buffer.lines.get(y).get(x)[1/*CHAR_DATA_CHAR_INDEX*/], + width: buffer.lines.get(y).get(x)[2/*CHAR_DATA_WIDTH_INDEX*/] + }; + }, + translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string { + return buffer.translateBufferLineToString(y, trimRight); + } + }; + } + }; + } pushWriteData(): void { this._core._innerWrite(); } } +interface IBuffer { + readonly cursorY: number; + readonly cursorX: number; + readonly viewportY: number; + readonly baseY: number; + readonly length: number; + getLine(y: number): IBufferLine | undefined; +} + +interface IBufferLine { + readonly isWrapped: boolean; + getCell(x: number): IBufferCell; + translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string; +} + +interface IBufferCell { + readonly char: string; + readonly width: number; +} + class TestSearchHelper extends SearchHelper { public findInLine(term: string, rowNumber: number, searchOptions?: ISearchOptions): ISearchResult { return this._findInLine(term, rowNumber, 0, searchOptions); From 0c588a7333e4f6d952f04ce15c86ed385b069c3d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 12 May 2019 09:45:09 -0700 Subject: [PATCH 74/97] Fix missed merge conflict --- src/Terminal.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 5dd765e2..def45f3a 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -43,11 +43,7 @@ import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager'; import { MouseZoneManager } from './MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './ui/ScreenDprMonitor'; -<<<<<<< HEAD -import { ITheme, IMarker, IDisposable, ITerminalAddon } from 'xterm'; -======= -import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm'; ->>>>>>> ups/master +import { ITheme, IMarker, IDisposable, ITerminalAddon, ISelectionPosition } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; From 523d562f73d84639e07d04665006f18498755f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 12 May 2019 18:49:23 +0200 Subject: [PATCH 75/97] fix missing types --- src/Terminal.ts | 2 +- src/core/input/TextDecoder.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 383b73f8..74688cf5 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1377,7 +1377,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II return; } - // Ignore falsy data values (including the empty string) + // Ignore falsy data values if (!data) { return; } diff --git a/src/core/input/TextDecoder.ts b/src/core/input/TextDecoder.ts index 75029822..7e141e02 100644 --- a/src/core/input/TextDecoder.ts +++ b/src/core/input/TextDecoder.ts @@ -28,7 +28,7 @@ export function utf32ToString(data: Uint32Array, start: number = 0, end: number for (let i = start; i < end; ++i) { let codepoint = data[i]; if (codepoint > 0xFFFF) { - // JS string are encoded as UTF16, thus a non BMP codepoint gets converted into a surrogate pair + // JS strings are encoded as UTF16, thus a non BMP codepoint gets converted into a surrogate pair // conversion rules: // - subtract 0x10000 from code point, leaving a 20 bit number // - add high 10 bits to 0xD800 --> first surrogate @@ -140,10 +140,10 @@ export class Utf8ToUtf32 { } let size = 0; - let byte1; - let byte2; - let byte3; - let byte4; + let byte1: number; + let byte2: number; + let byte3: number; + let byte4: number; let codepoint = 0; let startPos = 0; @@ -153,7 +153,7 @@ export class Utf8ToUtf32 { let cp = this.interim[0]; cp &= ((((cp & 0xE0) === 0xC0)) ? 0x1F : (((cp & 0xF0) === 0xE0)) ? 0x0F : 0x07); let pos = 0; - let tmp; + let tmp: number; while ((tmp = this.interim[++pos] & 0x3F) && pos < 4) { cp <<= 6; cp |= tmp; From eda04bcb2393ce9d9fcdd45552e47503b945b05f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 12 May 2019 10:20:38 -0700 Subject: [PATCH 76/97] Add new search addon, move AddonManager to public Because addons build on top of the API it needs to live in public, the main reason for this is because the implementation of buffer differs on public/Terminal and src/Terminal. --- demo/client.ts | 9 ++++++--- package.json | 1 + src/Terminal.ts | 10 +--------- src/Types.ts | 2 +- src/{ui => public}/AddonManager.test.ts | 0 src/{ui => public}/AddonManager.ts | 5 ++--- src/public/Terminal.ts | 6 +++++- yarn.lock | 5 +++++ 8 files changed, 21 insertions(+), 17 deletions(-) rename src/{ui => public}/AddonManager.test.ts (100%) rename src/{ui => public}/AddonManager.ts (87%) diff --git a/demo/client.ts b/demo/client.ts index a48b5582..b0ca23e8 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -9,6 +9,7 @@ import { Terminal } from '../lib/public/Terminal'; import { AttachAddon } from 'xterm-addon-attach'; +import { SearchAddon } from 'xterm-addon-search'; import { WebLinksAddon } from 'xterm-addon-web-links'; import * as fit from '../lib/addons/fit/fit'; @@ -28,10 +29,10 @@ declare let window: IWindowWithTerminal; Terminal.applyAddon(fit); Terminal.applyAddon(fullscreen); -Terminal.applyAddon(search); let term; let attachAddon: AttachAddon; +let searchAddon: SearchAddon; let protocol; let socketURL; let socket; @@ -95,6 +96,8 @@ function createTerminal(): void { typedTerm.loadAddon(new WebLinksAddon()); attachAddon = new AttachAddon(); typedTerm.loadAddon(attachAddon); + searchAddon = new SearchAddon(); + typedTerm.loadAddon(searchAddon); window.term = term; // Expose `term` to window for debugging purposes term.onResize((size: { cols: number, rows: number }) => { @@ -119,12 +122,12 @@ function createTerminal(): void { addDomListener(actionElements.findNext, 'keyup', (e) => { const searchOptions = getSearchOptions(); searchOptions.incremental = e.key !== `Enter`; - term.findNext(actionElements.findNext.value, searchOptions); + searchAddon.findNext(actionElements.findNext.value, searchOptions); }); addDomListener(actionElements.findPrevious, 'keyup', (e) => { if (e.key === `Enter`) { - term.findPrevious(actionElements.findPrevious.value, getSearchOptions()); + searchAddon.findPrevious(actionElements.findPrevious.value, getSearchOptions()); } }); diff --git a/package.json b/package.json index 7f3cfc66..55fbef6f 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "webpack": "^4.17.1", "webpack-cli": "^3.1.0", "xterm-addon-attach": "0.1.0-beta7", + "xterm-addon-search": "0.1.0-beta3", "xterm-addon-web-links": "0.1.0-beta6", "zmodem.js": "^0.1.5" }, diff --git a/src/Terminal.ts b/src/Terminal.ts index def45f3a..58e58b6e 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -43,14 +43,13 @@ import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager'; import { MouseZoneManager } from './MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './ui/ScreenDprMonitor'; -import { ITheme, IMarker, IDisposable, ITerminalAddon, ISelectionPosition } from 'xterm'; +import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; import { KeyboardResultType, ICharset, IBufferLine, IAttributeData } from './core/Types'; import { clone } from './common/Clone'; -import { AddonManager } from './ui/AddonManager'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; import { Attributes, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine'; import { applyWindowsMode } from './WindowsMode'; @@ -212,7 +211,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _mouseZoneManager: IMouseZoneManager; public mouseHelper: MouseHelper; private _accessibilityManager: AccessibilityManager; - private _addonManager: AddonManager; private _screenDprMonitor: ScreenDprMonitor; private _theme: ITheme; private _windowsMode: IDisposable | undefined; @@ -275,7 +273,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } public dispose(): void { - this._addonManager.dispose(); super.dispose(); if (this._windowsMode) { this._windowsMode.dispose(); @@ -361,7 +358,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.linkifier = this.linkifier || new Linkifier(this); this._mouseZoneManager = this._mouseZoneManager || null; this.soundManager = this.soundManager || new SoundManager(this); - this._addonManager = this._addonManager || new AddonManager(); // Create the terminal's buffers and set the current buffer this.buffers = new BufferSet(this); @@ -1973,10 +1969,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // return this.options.bellStyle === 'sound' || // this.options.bellStyle === 'both'; } - - public loadAddon(addon: ITerminalAddon): void { - return this._addonManager.loadAddon(this, addon); - } } /** diff --git a/src/Types.ts b/src/Types.ts index 939e8531..6136dafb 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -220,6 +220,7 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc showCursor(): void; } +// Portions of the public API that are required by the internal Terminal export interface IPublicTerminal extends IDisposable, IEventEmitter { textarea: HTMLTextAreaElement; rows: number; @@ -268,7 +269,6 @@ export interface IPublicTerminal extends IDisposable, IEventEmitter { setOption(key: string, value: any): void; refresh(start: number, end: number): void; reset(): void; - loadAddon(addon: ITerminalAddon): void; } export interface ITerminalAddon extends IDisposable { diff --git a/src/ui/AddonManager.test.ts b/src/public/AddonManager.test.ts similarity index 100% rename from src/ui/AddonManager.test.ts rename to src/public/AddonManager.test.ts diff --git a/src/ui/AddonManager.ts b/src/public/AddonManager.ts similarity index 87% rename from src/ui/AddonManager.ts rename to src/public/AddonManager.ts index 6821ac60..b66bd4b1 100644 --- a/src/ui/AddonManager.ts +++ b/src/public/AddonManager.ts @@ -3,8 +3,7 @@ * @license MIT */ -import { ITerminalAddon, IDisposable } from 'xterm'; -import { IPublicTerminal } from '../Types'; +import { ITerminalAddon, IDisposable, Terminal } from 'xterm'; export interface ILoadedAddon { instance: ITerminalAddon; @@ -24,7 +23,7 @@ export class AddonManager implements IDisposable { } } - public loadAddon(terminal: IPublicTerminal, instance: ITerminalAddon): void { + public loadAddon(terminal: Terminal, instance: ITerminalAddon): void { const loadedAddon: ILoadedAddon = { instance, dispose: instance.dispose, diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 33f346ba..ce13c9b0 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -9,12 +9,15 @@ import { IBufferLine } from '../core/Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; import { IEvent } from '../common/EventEmitter2'; +import { AddonManager } from './AddonManager'; export class Terminal implements ITerminalApi { private _core: ITerminal; + private _addonManager: AddonManager; constructor(options?: ITerminalOptions) { this._core = new TerminalCore(options); + this._addonManager = new AddonManager(); } public get onCursorMove(): IEvent { return this._core.onCursorMove; } @@ -115,6 +118,7 @@ export class Terminal implements ITerminalApi { this._core.selectLines(start, end); } public dispose(): void { + this._addonManager.dispose(); this._core.dispose(); } public destroy(): void { @@ -174,7 +178,7 @@ export class Terminal implements ITerminalApi { addon.apply(Terminal); } public loadAddon(addon: ITerminalAddon): void { - return this._core.loadAddon(addon); + return this._addonManager.loadAddon(this, addon); } public static get strings(): ILocalizableStrings { return Strings; diff --git a/yarn.lock b/yarn.lock index 7312d587..eaab4512 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7231,6 +7231,11 @@ xterm-addon-attach@0.1.0-beta7: resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.1.0-beta7.tgz#787f6cce709611ee08ab731b95a62fa1c0bce6a9" integrity sha512-nQr6LcYtpZcyDoHyL/BDIPJcTgL7qlHR/rvm8lSizQysGVT0pSzr5M7SjY3kQHw33U3hTer3c6oZzwjfj4ohOw== +xterm-addon-search@0.1.0-beta3: + version "0.1.0-beta3" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.1.0-beta3.tgz#0754fa329cd505d6591abf24aac560c72f865636" + integrity sha512-09w/h3wsFtCveH1C0Fu8dwVvjiNvWRgp2lDABSK/yQEGETq4nznLzRSiMnFMz1y1rilFUi3Xn+l4+tQK+8iORg== + xterm-addon-web-links@0.1.0-beta6: version "0.1.0-beta6" resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta6.tgz#9b4e862be8928ef455a667745bea479665db6c6b" From d230f93bdecf5b04bc4a96056d060ae0467adaad Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 12 May 2019 10:25:41 -0700 Subject: [PATCH 77/97] Remove old search addon from demo --- demo/client.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index b0ca23e8..fff57379 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -9,13 +9,11 @@ import { Terminal } from '../lib/public/Terminal'; import { AttachAddon } from 'xterm-addon-attach'; -import { SearchAddon } from 'xterm-addon-search'; +import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; import { WebLinksAddon } from 'xterm-addon-web-links'; import * as fit from '../lib/addons/fit/fit'; import * as fullscreen from '../lib/addons/fullscreen/fullscreen'; -import * as search from '../lib/addons/search/search'; -import { ISearchOptions } from '../lib/addons/search/Interfaces'; // Pulling in the module's types relies on the above, it's looks a // little weird here as we're importing "this" module From caf8f9d35ea52ffbe2c7273f0fd2725fea280319 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 12 May 2019 10:39:39 -0700 Subject: [PATCH 78/97] Update search addon --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 55fbef6f..832b479f 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "webpack": "^4.17.1", "webpack-cli": "^3.1.0", "xterm-addon-attach": "0.1.0-beta7", - "xterm-addon-search": "0.1.0-beta3", + "xterm-addon-search": "0.1.0-beta4", "xterm-addon-web-links": "0.1.0-beta6", "zmodem.js": "^0.1.5" }, diff --git a/yarn.lock b/yarn.lock index eaab4512..666dae16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7231,10 +7231,10 @@ xterm-addon-attach@0.1.0-beta7: resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.1.0-beta7.tgz#787f6cce709611ee08ab731b95a62fa1c0bce6a9" integrity sha512-nQr6LcYtpZcyDoHyL/BDIPJcTgL7qlHR/rvm8lSizQysGVT0pSzr5M7SjY3kQHw33U3hTer3c6oZzwjfj4ohOw== -xterm-addon-search@0.1.0-beta3: - version "0.1.0-beta3" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.1.0-beta3.tgz#0754fa329cd505d6591abf24aac560c72f865636" - integrity sha512-09w/h3wsFtCveH1C0Fu8dwVvjiNvWRgp2lDABSK/yQEGETq4nznLzRSiMnFMz1y1rilFUi3Xn+l4+tQK+8iORg== +xterm-addon-search@0.1.0-beta4: + version "0.1.0-beta4" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.1.0-beta4.tgz#c73fe058c87f07eaae31baaa92976e927438a396" + integrity sha512-tJgZ1VTRd/DOFUhSFZzybRF8SR1LCEXRYkw/mHzGV5Ba3zhqVdSkN/0J9sjOpX6u21buee2OmTiCMZxq80zfJg== xterm-addon-web-links@0.1.0-beta6: version "0.1.0-beta6" From 1502fea43159f8342eda3c82676d6ef095fde62d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 12 May 2019 10:41:01 -0700 Subject: [PATCH 79/97] Remove unused fullscreen addon from demo --- demo/client.ts | 2 -- demo/index.html | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index fff57379..2d7c800d 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -13,7 +13,6 @@ import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; import { WebLinksAddon } from 'xterm-addon-web-links'; import * as fit from '../lib/addons/fit/fit'; -import * as fullscreen from '../lib/addons/fullscreen/fullscreen'; // Pulling in the module's types relies on the above, it's looks a // little weird here as we're importing "this" module @@ -26,7 +25,6 @@ export interface IWindowWithTerminal extends Window { declare let window: IWindowWithTerminal; Terminal.applyAddon(fit); -Terminal.applyAddon(fullscreen); let term; let attachAddon: AttachAddon; diff --git a/demo/index.html b/demo/index.html index 370a51ed..a7ab0f0c 100644 --- a/demo/index.html +++ b/demo/index.html @@ -3,7 +3,6 @@ xterm.js demo - @@ -16,7 +15,7 @@

- +

From f90239377bd7bd457af00427d9703481e8892015 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 12 May 2019 10:47:35 -0700 Subject: [PATCH 80/97] Fix possible infinite loop if addon disposes terminal --- src/public/AddonManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/AddonManager.ts b/src/public/AddonManager.ts index b66bd4b1..b5506514 100644 --- a/src/public/AddonManager.ts +++ b/src/public/AddonManager.ts @@ -49,8 +49,8 @@ export class AddonManager implements IDisposable { if (index === -1) { throw new Error('Could not dispose an addon that has not been loaded'); } - loadedAddon.dispose(); loadedAddon.isDisposed = true; + loadedAddon.dispose(); this._addons.splice(index, 1); } } From 347576494658ba49f63964a100544fc8ddfd464e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 12 May 2019 10:59:26 -0700 Subject: [PATCH 81/97] Types/doc polish --- src/TestUtils.test.ts | 5 +---- src/Types.ts | 6 +----- typings/xterm.d.ts | 3 +++ 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 109bebd2..3f53c7d3 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -9,7 +9,7 @@ import { IBufferLine, ICellData, IAttributeData } from './core/Types'; import { ICircularList, XtermListener } from './common/Types'; import { Buffer } from './Buffer'; import * as Browser from './common/Platform'; -import { ITheme, IDisposable, IMarker, IEvent, ITerminalAddon, ISelectionPosition } from 'xterm'; +import { ITheme, IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; import { AttributeData } from './core/buffer/BufferLine'; @@ -30,9 +30,6 @@ export class MockTerminal implements ITerminal { onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>; onRender: IEvent<{ start: number; end: number; }>; onResize: IEvent<{ cols: number; rows: number; }>; - loadAddon(addon: ITerminalAddon): void { - throw new Error('Method not implemented.'); - } markers: IMarker[]; addMarker(cursorYOffset: number): IMarker { throw new Error('Method not implemented.'); diff --git a/src/Types.ts b/src/Types.ts index 6136dafb..4ffcf7e8 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker, Terminal, ISelectionPosition } from 'xterm'; +import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker, ISelectionPosition } from 'xterm'; import { IColorSet, IRenderer } from './renderer/Types'; import { ICharset, IAttributeData, ICellData, IBufferLine, CharData } from './core/Types'; import { ICircularList } from './common/Types'; @@ -271,10 +271,6 @@ export interface IPublicTerminal extends IDisposable, IEventEmitter { reset(): void; } -export interface ITerminalAddon extends IDisposable { - activate(terminal: Terminal): void; -} - export interface IBufferAccessor { buffer: IBuffer; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 89d2b62b..c059b480 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -884,6 +884,9 @@ declare module 'xterm' { loadAddon(addon: ITerminalAddon): void; } + /** + * An addon that can provide additional functionality to the terminal. + */ export interface ITerminalAddon extends IDisposable { /** * (EXPERIMENTAL) This is called when the addon is activated within xterm.js. From bda068c5f04f276bd2742defcf7fef83386fc03a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 12 May 2019 22:07:14 +0200 Subject: [PATCH 82/97] docs --- typings/xterm.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 73c7aeed..9afac941 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -759,7 +759,9 @@ declare module 'xterm' { writeln(data: string): void; /** - * Writes text to the terminal encoded as UTF-8 to the terminal. + * Writes UTF8 data to the terminal. + * This has a slight performance advantage over the string based write method + * due to lesser data conversions needed on the way from the pty to xterm.js. * @param data The data to write to the terminal. */ writeUtf8(data: Uint8Array): void; From c0e9bbe87f6a606aee25341547cb5514ee50aea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 12 May 2019 22:17:06 +0200 Subject: [PATCH 83/97] Revert "change demo to utf8 input" This reverts commit e6e5ecc0f2c4742e781b0f6c7b95caaaae6a024f. --- demo/client.ts | 1 - demo/server.js | 13 ++++++------- src/addons/attach/attach.ts | 5 ----- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index f91c535f..2d7c800d 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -143,7 +143,6 @@ function createTerminal(): void { pid = processId; socketURL += processId; socket = new WebSocket(socketURL); - socket.binaryType = 'arraybuffer'; socket.onopen = runRealTerminal; socket.onclose = runFakeTerminal; socket.onerror = runFakeTerminal; diff --git a/demo/server.js b/demo/server.js index 08413941..8270a398 100644 --- a/demo/server.js +++ b/demo/server.js @@ -36,8 +36,7 @@ function startServer() { cols: cols || 80, rows: rows || 24, cwd: process.env.PWD, - env: process.env, - encoding: null + env: process.env }); console.log('Created terminal with PID: ' + term.pid); @@ -67,20 +66,20 @@ function startServer() { ws.send(logs[term.pid]); function buffer(socket, timeout) { - let buffer = []; + let s = ''; let sender = null; return (data) => { - buffer.push(data); + s += data; if (!sender) { sender = setTimeout(() => { - socket.send(Buffer.concat(buffer)); - buffer = []; + socket.send(s); + s = ''; sender = null; }, timeout); } }; } - const send = buffer(ws, 5); + const send = buffer(ws, 5); term.on('data', function(data) { try { diff --git a/src/addons/attach/attach.ts b/src/addons/attach/attach.ts index 0f5d2ea1..2c8a5d4d 100644 --- a/src/addons/attach/attach.ts +++ b/src/addons/attach/attach.ts @@ -42,11 +42,6 @@ export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean addonTerminal.__getMessage = function(ev: MessageEvent): void { let str: string; - if (ev.data instanceof ArrayBuffer) { - addonTerminal.writeUtf8(new Uint8Array(ev.data)); - return; - } - if (typeof ev.data === 'object') { if (!myTextDecoder) { myTextDecoder = new TextDecoder(); From 6fd5cdd8f1b28e5e643d4b1d598a8832f9d3a4fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 13 May 2019 00:58:14 +0200 Subject: [PATCH 84/97] apply attach changes to demo --- demo/client.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 2d7c800d..affcf64c 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -27,7 +27,6 @@ declare let window: IWindowWithTerminal; Terminal.applyAddon(fit); let term; -let attachAddon: AttachAddon; let searchAddon: SearchAddon; let protocol; let socketURL; @@ -90,8 +89,6 @@ function createTerminal(): void { // Load addons const typedTerm = term as TerminalType; typedTerm.loadAddon(new WebLinksAddon()); - attachAddon = new AttachAddon(); - typedTerm.loadAddon(attachAddon); searchAddon = new SearchAddon(); typedTerm.loadAddon(searchAddon); @@ -152,7 +149,7 @@ function createTerminal(): void { } function runRealTerminal(): void { - attachAddon.attach(socket); + term.loadAddon(new AttachAddon(socket)); term._initialized = true; } From 53167df1a800000f6121bf95bc9adf44c30e181e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 13 May 2019 01:24:41 +0200 Subject: [PATCH 85/97] document utf8 switch in client.ts --- demo/client.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/demo/client.ts b/demo/client.ts index affcf64c..f9a6f6ff 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -149,7 +149,14 @@ function createTerminal(): void { } function runRealTerminal(): void { + /** + * The demo defaults to string transport by default. + * To run it with UTF8 binary transport, swap comment on + * the lines below. (Must also be switched in server.js) + */ term.loadAddon(new AttachAddon(socket)); + //term.loadAddon(new AttachAddon(socket, {inputUtf8: true})); + term._initialized = true; } From 67be8c36e75e735f8ed9bb590ccbeb9158922b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 13 May 2019 01:40:54 +0200 Subject: [PATCH 86/97] utf8 switch in server.js --- demo/client.ts | 4 ++-- demo/server.js | 28 ++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index f9a6f6ff..685f633b 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -154,8 +154,8 @@ function runRealTerminal(): void { * To run it with UTF8 binary transport, swap comment on * the lines below. (Must also be switched in server.js) */ - term.loadAddon(new AttachAddon(socket)); - //term.loadAddon(new AttachAddon(socket, {inputUtf8: true})); + //term.loadAddon(new AttachAddon(socket)); + term.loadAddon(new AttachAddon(socket, {inputUtf8: true})); term._initialized = true; } diff --git a/demo/server.js b/demo/server.js index 8270a398..718327e4 100644 --- a/demo/server.js +++ b/demo/server.js @@ -3,6 +3,13 @@ var expressWs = require('express-ws'); var os = require('os'); var pty = require('node-pty'); +/** + * Whether to use UTF8 binary transport. + * (Must also be switched in client.ts) + */ +const USE_BINARY_UTF8 = true; + + function startServer() { var app = express(); expressWs(app); @@ -36,7 +43,8 @@ function startServer() { cols: cols || 80, rows: rows || 24, cwd: process.env.PWD, - env: process.env + env: process.env, + encoding: USE_BINARY_UTF8 ? null : 'utf8' }); console.log('Created terminal with PID: ' + term.pid); @@ -65,6 +73,7 @@ function startServer() { console.log('Connected to terminal ' + term.pid); ws.send(logs[term.pid]); + // string message buffering function buffer(socket, timeout) { let s = ''; let sender = null; @@ -79,7 +88,22 @@ function startServer() { } }; } - const send = buffer(ws, 5); + // binary message buffering + function bufferUtf8(socket, timeout) { + let buffer = []; + let sender = null; + return (data) => { + buffer.push(data); + if (!sender) { + sender = setTimeout(() => { + socket.send(Buffer.concat(buffer)); + buffer = []; + sender = null; + }, timeout); + } + }; + } + const send = USE_BINARY_UTF8 ? bufferUtf8(ws, 5) : buffer(ws, 5); term.on('data', function(data) { try { From 1a596f9608b0ca337be5d81b1d8b0a851006eb8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 13 May 2019 01:41:51 +0200 Subject: [PATCH 87/97] default to string transport --- demo/client.ts | 4 ++-- demo/server.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 685f633b..f9a6f6ff 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -154,8 +154,8 @@ function runRealTerminal(): void { * To run it with UTF8 binary transport, swap comment on * the lines below. (Must also be switched in server.js) */ - //term.loadAddon(new AttachAddon(socket)); - term.loadAddon(new AttachAddon(socket, {inputUtf8: true})); + term.loadAddon(new AttachAddon(socket)); + //term.loadAddon(new AttachAddon(socket, {inputUtf8: true})); term._initialized = true; } diff --git a/demo/server.js b/demo/server.js index 718327e4..823dc6c6 100644 --- a/demo/server.js +++ b/demo/server.js @@ -7,7 +7,7 @@ var pty = require('node-pty'); * Whether to use UTF8 binary transport. * (Must also be switched in client.ts) */ -const USE_BINARY_UTF8 = true; +const USE_BINARY_UTF8 = false; function startServer() { From 69411916cb84c4572837d7f0cfee4e3d8913f0cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 13 May 2019 01:45:21 +0200 Subject: [PATCH 88/97] make linter happy --- demo/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/client.ts b/demo/client.ts index f9a6f6ff..01789402 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -155,7 +155,7 @@ function runRealTerminal(): void { * the lines below. (Must also be switched in server.js) */ term.loadAddon(new AttachAddon(socket)); - //term.loadAddon(new AttachAddon(socket, {inputUtf8: true})); + // term.loadAddon(new AttachAddon(socket, {inputUtf8: true})); term._initialized = true; } From 58d461bb9047c9282e627a5ea323fef863112ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 13 May 2019 15:33:54 +0200 Subject: [PATCH 89/97] optimize Buffer.concat --- demo/server.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/demo/server.js b/demo/server.js index 823dc6c6..e3473400 100644 --- a/demo/server.js +++ b/demo/server.js @@ -92,13 +92,16 @@ function startServer() { function bufferUtf8(socket, timeout) { let buffer = []; let sender = null; + let length = 0; return (data) => { buffer.push(data); + length += data.length; if (!sender) { sender = setTimeout(() => { - socket.send(Buffer.concat(buffer)); + socket.send(Buffer.concat(buffer, length)); buffer = []; sender = null; + length = 0; }, timeout); } }; From b9cfbbea8fb96fd6d9201d016c5edc07c728d749 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 13 May 2019 20:34:14 -0700 Subject: [PATCH 90/97] xterm-addon-attach@0.1.0-beta8 --- package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index cafa43b1..043b76cb 100644 --- a/package.json +++ b/package.json @@ -40,13 +40,13 @@ "ts-loader": "^4.5.0", "tslint": "^5.9.1", "tslint-consistent-codestyle": "^1.13.0", - "utf8": "^3.0.0", "typescript": "3.4", + "utf8": "^3.0.0", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", "webpack": "^4.17.1", "webpack-cli": "^3.1.0", - "xterm-addon-attach": "0.1.0-beta7", + "xterm-addon-attach": "0.1.0-beta8", "xterm-addon-search": "0.1.0-beta4", "xterm-addon-web-links": "0.1.0-beta6", "zmodem.js": "^0.1.5" diff --git a/yarn.lock b/yarn.lock index 3db8e9a4..321fe6f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7236,10 +7236,10 @@ xregexp@4.0.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= -xterm-addon-attach@0.1.0-beta7: - version "0.1.0-beta7" - resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.1.0-beta7.tgz#787f6cce709611ee08ab731b95a62fa1c0bce6a9" - integrity sha512-nQr6LcYtpZcyDoHyL/BDIPJcTgL7qlHR/rvm8lSizQysGVT0pSzr5M7SjY3kQHw33U3hTer3c6oZzwjfj4ohOw== +xterm-addon-attach@0.1.0-beta8: + version "0.1.0-beta8" + resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.1.0-beta8.tgz#e469ed9d6ab7e535d0a9ffae23ef4f2efe58163b" + integrity sha512-HtQuwqnvcR+SwI9/JbBMd//Il+oEeo3rWrIucLLKHT8sB+OAOkdhmo5KIM/hhnovjI040WJ+tTHkDgPFwIJtmw== xterm-addon-search@0.1.0-beta4: version "0.1.0-beta4" From f331269ef4236ee4368da68a66101eecf30bf3ea Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 13 May 2019 21:11:38 -0700 Subject: [PATCH 91/97] Support debugging integration tests --- .vscode/launch.json | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index e5bad7b3..f0bc9be8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -13,7 +13,25 @@ "runtimeArgs": [ "--colors", "--recursive", - "${workspaceRoot}/lib" + "${workspaceRoot}/lib/**/*.test.js" + ], + "sourceMaps": true, + "outFiles": [ "${workspaceRoot}/lib/**/*.js" ], + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "node", + "request": "launch", + "name": "Integration Tests", + "cwd": "${workspaceRoot}", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha.cmd" + }, + "runtimeArgs": [ + "--colors", + "--recursive", + "${workspaceRoot}/lib/**/*.api.js" ], "sourceMaps": true, "outFiles": [ "${workspaceRoot}/lib/**/*.js" ], From 80025804b3785b880e8b3f2c81bfb298d2364d5c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 15 May 2019 11:47:15 -0700 Subject: [PATCH 92/97] Add sanity check in AccessibilityManager Fixes #2082 --- src/AccessibilityManager.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 1f44c132..4351a336 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -249,9 +249,11 @@ export class AccessibilityManager extends Disposable { const lineData = buffer.translateBufferLineToString(buffer.ydisp + i, true); const posInSet = (buffer.ydisp + i + 1).toString(); const element = this._rowElements[i]; - element.textContent = lineData.length === 0 ? Strings.blankLine : lineData; - element.setAttribute('aria-posinset', posInSet); - element.setAttribute('aria-setsize', setSize); + if (element) { + element.textContent = lineData.length === 0 ? Strings.blankLine : lineData; + element.setAttribute('aria-posinset', posInSet); + element.setAttribute('aria-setsize', setSize); + } } } From da79fec5c2fa0dd7ed7ca23901e7e24e58f11812 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 15 May 2019 11:57:32 -0700 Subject: [PATCH 93/97] Disable failing test on Linux --- src/Terminal.integration.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index 5e12d2a3..84dfbf8b 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -113,8 +113,12 @@ if (os.platform() !== 'win32') { 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 63, 68 ]; + // These are failing on Linux only + if (os.platform() === 'linux') { + skip.push(0); + } + // These are failing on macOS only if (os.platform() === 'darwin') { - // These are failing on macOS only skip.push(3, 7, 11, 67); } for (let i = 0; i < files.length; i++) { From a62ec679bdaaba07da6c709d4c79e6f054032cdb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 15 May 2019 12:49:13 -0700 Subject: [PATCH 94/97] Move readFileSync out of test --- src/Terminal.integration.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index 84dfbf8b..fa4403f4 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -113,10 +113,6 @@ if (os.platform() !== 'win32') { 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 63, 68 ]; - // These are failing on Linux only - if (os.platform() === 'linux') { - skip.push(0); - } // These are failing on macOS only if (os.platform() === 'darwin') { skip.push(3, 7, 11, 67); @@ -126,9 +122,9 @@ if (os.platform() !== 'win32') { continue; } ((filename: string) => { + const inFile = fs.readFileSync(filename, 'utf8'); it(filename.split('/').slice(-1)[0], done => { ptyReset(() => { - const inFile = fs.readFileSync(filename, 'utf8'); ptyWriteRead(inFile, fromPty => { // uncomment this to get log from terminal // console.log = function(){}; From e2214b02394b7cf10777c0514eb29015209a281f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 16 May 2019 10:39:53 -0700 Subject: [PATCH 95/97] Fix what branch coverage is sent with --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fd65a6a8..f6acee37 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -50,7 +50,7 @@ jobs: displayName: 'Lint' - script: | yarn test-coverage - export COVERALLS_GIT_BRANCH=$BUILD_SOURCEBRANCH + export COVERALLS_GIT_BRANCH=$BUILD_SOURCEBRANCHNAME yarn coveralls displayName: 'Generate and publish coverage' From 88c1ff6f11292641547f606cf156be0e43177bfa Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 17 May 2019 23:37:44 -0700 Subject: [PATCH 96/97] Move BufferReflow to core --- src/Buffer.ts | 2 +- src/{ => core/buffer}/BufferReflow.test.ts | 64 +++++++++++----------- src/{ => core/buffer}/BufferReflow.ts | 6 +- 3 files changed, 36 insertions(+), 36 deletions(-) rename src/{ => core/buffer}/BufferReflow.test.ts (70%) rename src/{ => core/buffer}/BufferReflow.ts (97%) diff --git a/src/Buffer.ts b/src/Buffer.ts index d9117fda..14fe8acb 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -8,7 +8,7 @@ import { ITerminal, IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIt import { IBufferLine, ICellData, IAttributeData } from './core/Types'; import { IMarker } from 'xterm'; import { BufferLine, CellData, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine'; -import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from './BufferReflow'; +import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from './core/buffer/BufferReflow'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; import { Disposable } from './common/Lifecycle'; diff --git a/src/BufferReflow.test.ts b/src/core/buffer/BufferReflow.test.ts similarity index 70% rename from src/BufferReflow.test.ts rename to src/core/buffer/BufferReflow.test.ts index 91454e2a..d0d97dff 100644 --- a/src/BufferReflow.test.ts +++ b/src/core/buffer/BufferReflow.test.ts @@ -3,17 +3,17 @@ * @license MIT */ import { assert } from 'chai'; -import { BufferLine, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './core/buffer/BufferLine'; +import { BufferLine, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './BufferLine'; import { reflowSmallerGetNewLineLengths } from './BufferReflow'; describe('BufferReflow', () => { describe('reflowSmallerGetNewLineLengths', () => { it('should return correct line lengths for a small line with wide characters', () => { const line = new BufferLine(4); - line.set(0, [null, '汉', 2, '汉'.charCodeAt(0)]); - line.set(1, [null, '', 0, undefined]); - line.set(2, [null, '语', 2, '语'.charCodeAt(0)]); - line.set(3, [null, '', 0, undefined]); + line.set(0, [0, '汉', 2, '汉'.charCodeAt(0)]); + line.set(1, [0, '', 0, 0]); + line.set(2, [0, '语', 2, '语'.charCodeAt(0)]); + line.set(3, [0, '', 0, 0]); assert.equal(line.translateToString(true), '汉语'); assert.deepEqual(reflowSmallerGetNewLineLengths([line], 4, 3), [2, 2], 'line: 汉, 语'); assert.deepEqual(reflowSmallerGetNewLineLengths([line], 4, 2), [2, 2], 'line: 汉, 语'); @@ -21,12 +21,12 @@ describe('BufferReflow', () => { it('should return correct line lengths for a large line with wide characters', () => { const line = new BufferLine(12); for (let i = 0; i < 12; i += 4) { - line.set(i, [null, '汉', 2, '汉'.charCodeAt(0)]); - line.set(i + 2, [null, '语', 2, '语'.charCodeAt(0)]); + line.set(i, [0, '汉', 2, '汉'.charCodeAt(0)]); + line.set(i + 2, [0, '语', 2, '语'.charCodeAt(0)]); } for (let i = 1; i < 12; i += 2) { - line.set(i, [null, '', 0, undefined]); - line.set(i, [null, '', 0, undefined]); + line.set(i, [0, '', 0, 0]); + line.set(i, [0, '', 0, 0]); } assert.equal(line.translateToString(), '汉语汉语汉语'); assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 11), [10, 2], 'line: 汉语汉语汉, 语'); @@ -42,12 +42,12 @@ describe('BufferReflow', () => { }); it('should return correct line lengths for a string with wide and single characters', () => { const line = new BufferLine(6); - line.set(0, [null, 'a', 1, 'a'.charCodeAt(0)]); - line.set(1, [null, '汉', 2, '汉'.charCodeAt(0)]); - line.set(2, [null, '', 0, undefined]); - line.set(3, [null, '语', 2, '语'.charCodeAt(0)]); - line.set(4, [null, '', 0, undefined]); - line.set(5, [null, 'b', 1, 'b'.charCodeAt(0)]); + line.set(0, [0, 'a', 1, 'a'.charCodeAt(0)]); + line.set(1, [0, '汉', 2, '汉'.charCodeAt(0)]); + line.set(2, [0, '', 0, 0]); + line.set(3, [0, '语', 2, '语'.charCodeAt(0)]); + line.set(4, [0, '', 0, 0]); + line.set(5, [0, 'b', 1, 'b'.charCodeAt(0)]); assert.equal(line.translateToString(), 'a汉语b'); assert.deepEqual(reflowSmallerGetNewLineLengths([line], 6, 5), [5, 1], 'line: a汉语b'); assert.deepEqual(reflowSmallerGetNewLineLengths([line], 6, 4), [3, 3], 'line: a汉, 语b'); @@ -56,19 +56,19 @@ describe('BufferReflow', () => { }); it('should return correct line lengths for a wrapped line with wide and single characters', () => { const line1 = new BufferLine(6); - line1.set(0, [null, 'a', 1, 'a'.charCodeAt(0)]); - line1.set(1, [null, '汉', 2, '汉'.charCodeAt(0)]); - line1.set(2, [null, '', 0, undefined]); - line1.set(3, [null, '语', 2, '语'.charCodeAt(0)]); - line1.set(4, [null, '', 0, undefined]); - line1.set(5, [null, 'b', 1, 'b'.charCodeAt(0)]); + line1.set(0, [0, 'a', 1, 'a'.charCodeAt(0)]); + line1.set(1, [0, '汉', 2, '汉'.charCodeAt(0)]); + line1.set(2, [0, '', 0, 0]); + line1.set(3, [0, '语', 2, '语'.charCodeAt(0)]); + line1.set(4, [0, '', 0, 0]); + line1.set(5, [0, 'b', 1, 'b'.charCodeAt(0)]); const line2 = new BufferLine(6, undefined, true); - line2.set(0, [null, 'a', 1, 'a'.charCodeAt(0)]); - line2.set(1, [null, '汉', 2, '汉'.charCodeAt(0)]); - line2.set(2, [null, '', 0, undefined]); - line2.set(3, [null, '语', 2, '语'.charCodeAt(0)]); - line2.set(4, [null, '', 0, undefined]); - line2.set(5, [null, 'b', 1, 'b'.charCodeAt(0)]); + line2.set(0, [0, 'a', 1, 'a'.charCodeAt(0)]); + line2.set(1, [0, '汉', 2, '汉'.charCodeAt(0)]); + line2.set(2, [0, '', 0, 0]); + line2.set(3, [0, '语', 2, '语'.charCodeAt(0)]); + line2.set(4, [0, '', 0, 0]); + line2.set(5, [0, 'b', 1, 'b'.charCodeAt(0)]); assert.equal(line1.translateToString(), 'a汉语b'); assert.equal(line2.translateToString(), 'a汉语b'); assert.deepEqual(reflowSmallerGetNewLineLengths([line1, line2], 6, 5), [5, 4, 3], 'lines: a汉语, ba汉, 语b'); @@ -78,11 +78,11 @@ describe('BufferReflow', () => { }); it('should work on lines ending in null space', () => { const line = new BufferLine(5); - line.set(0, [null, '汉', 2, '汉'.charCodeAt(0)]); - line.set(1, [null, '', 0, undefined]); - line.set(2, [null, '语', 2, '语'.charCodeAt(0)]); - line.set(3, [null, '', 0, undefined]); - line.set(4, [null, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + line.set(0, [0, '汉', 2, '汉'.charCodeAt(0)]); + line.set(1, [0, '', 0, 0]); + line.set(2, [0, '语', 2, '语'.charCodeAt(0)]); + line.set(3, [0, '', 0, 0]); + line.set(4, [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); assert.equal(line.translateToString(true), '汉语'); assert.equal(line.translateToString(false), '汉语 '); assert.deepEqual(reflowSmallerGetNewLineLengths([line], 4, 3), [2, 2], 'line: 汉, 语'); diff --git a/src/BufferReflow.ts b/src/core/buffer/BufferReflow.ts similarity index 97% rename from src/BufferReflow.ts rename to src/core/buffer/BufferReflow.ts index c1068967..e363da0b 100644 --- a/src/BufferReflow.ts +++ b/src/core/buffer/BufferReflow.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { BufferLine } from './core/buffer/BufferLine'; -import { CircularList } from './common/CircularList'; -import { IBufferLine, ICellData } from './core/Types'; +import { BufferLine } from './BufferLine'; +import { CircularList } from '../../common/CircularList'; +import { IBufferLine, ICellData } from '../Types'; export interface INewLayoutResult { layout: number[]; From dbafc793da6a48579daa454b1651f56440e8e8f6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 17 May 2019 23:41:26 -0700 Subject: [PATCH 97/97] Move Marker to core --- src/Buffer.ts | 31 +------------------------------ src/core/Types.ts | 8 ++++++++ src/core/buffer/Marker.ts | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 30 deletions(-) create mode 100644 src/core/buffer/Marker.ts diff --git a/src/Buffer.ts b/src/Buffer.ts index 14fe8acb..37d61d88 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -6,11 +6,9 @@ import { CircularList, IInsertEvent } from './common/CircularList'; import { ITerminal, IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from './Types'; import { IBufferLine, ICellData, IAttributeData } from './core/Types'; -import { IMarker } from 'xterm'; import { BufferLine, CellData, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine'; import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from './core/buffer/BufferReflow'; -import { EventEmitter2, IEvent } from './common/EventEmitter2'; -import { Disposable } from './common/Lifecycle'; +import { Marker } from './core/buffer/Marker'; export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 @@ -603,33 +601,6 @@ export class Buffer implements IBuffer { } } -export class Marker extends Disposable implements IMarker { - private static _nextId = 1; - - private _id: number = Marker._nextId++; - public isDisposed: boolean = false; - - public get id(): number { return this._id; } - - private _onDispose = new EventEmitter2(); - public get onDispose(): IEvent { return this._onDispose.event; } - - constructor( - public line: number - ) { - super(); - } - - public dispose(): void { - if (this.isDisposed) { - return; - } - this.isDisposed = true; - // Emit before super.dispose such that dispose listeners get a change to react - this._onDispose.fire(); - } -} - /** * Iterator to get unwrapped content strings from the buffer. * The iterator returns at least the string data between the borders diff --git a/src/core/Types.ts b/src/core/Types.ts index ae9274b8..5b97f249 100644 --- a/src/core/Types.ts +++ b/src/core/Types.ts @@ -3,6 +3,8 @@ * @license MIT */ +import { IDisposable } from '../common/Types'; + export const enum KeyboardResultType { SEND_KEY, SELECT_ALL, @@ -98,3 +100,9 @@ export interface IBufferLine { isCombined(index: number): number; getString(index: number): string; } + +export interface IMarker extends IDisposable { + readonly id: number; + readonly isDisposed: boolean; + readonly line: number; +} diff --git a/src/core/buffer/Marker.ts b/src/core/buffer/Marker.ts new file mode 100644 index 00000000..26de0dfe --- /dev/null +++ b/src/core/buffer/Marker.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { EventEmitter2, IEvent } from '../../common/EventEmitter2'; +import { Disposable } from '../../common/Lifecycle'; +import { IMarker } from '../Types'; + +export class Marker extends Disposable implements IMarker { + private static _nextId = 1; + + private _id: number = Marker._nextId++; + public isDisposed: boolean = false; + + public get id(): number { return this._id; } + + private _onDispose = new EventEmitter2(); + public get onDispose(): IEvent { return this._onDispose.event; } + + constructor( + public line: number + ) { + super(); + } + + public dispose(): void { + if (this.isDisposed) { + return; + } + this.isDisposed = true; + // Emit before super.dispose such that dispose listeners get a change to react + this._onDispose.fire(); + } +}