diff --git a/AUTHORS b/AUTHORS index 984b896f..1bd4c87d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -19,45 +19,63 @@ Bill Church Bob Reid bottleofwater Brian Mock +Bruno Ribeito Carson Anderson CHaBou Christian Budde Christensen Christof Marti Christopher Jeffrey coderaiser +Dan Brown +Daniel Griffen Daniel Imms Daniel Risacher Dan Kaplun Darin Morrison +dcylabs +Dominik Csapak Edgar Andrés Margffoy Tuay Elliot Saba +Exile +Felipe Gasper +ficristo Gary Ritchie hiro-su Ian Lewis imoses InDieTasten irokas +Jakob Gillich Jean Bruenn Jeremy Danyow Joao Moreno Joao Moreno +Johannes Zellner Jon Masters Jörg Breitbart Justin Mecham Kirill Merkushev +Luca Lucian Buzzo +Lukas Drgon Maël Nison +Marc Dumais Martin Chloride +Martin Koppehel Martin Wang Matt Bierner Michael Irwin Mikko Karvonen mofux Nicolas Ramz +npezza93 Oleksandr Andriienko Paris Kasidiaris Paris Kasidiaris +Peter Baumgarten +Rick Baker runarberg +Samuel Williams Saswat Das Saul Costa Shuanglei Tao diff --git a/README.md b/README.md index 3ff9b205..1719efb2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # [![xterm.js logo](logo-full.png)](https://xtermjs.org) -[![xterm.js build status](https://api.travis-ci.org/sourcelair/xterm.js.svg)](https://travis-ci.org/sourcelair/xterm.js) [![Coverage Status](https://coveralls.io/repos/github/sourcelair/xterm.js/badge.svg)](https://coveralls.io/github/sourcelair/xterm.js) [![Gitter](https://badges.gitter.im/sourcelair/xterm.js.svg)](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/xterm/badge?style=rounded)](https://www.jsdelivr.com/package/npm/xterm) +[![xterm.js build status](https://api.travis-ci.org/xtermjs/xterm.js.svg)](https://travis-ci.org/xtermjs/xterm.js) [![Coverage Status](https://coveralls.io/repos/github/sourcelair/xterm.js/badge.svg)](https://coveralls.io/github/sourcelair/xterm.js) [![Gitter](https://badges.gitter.im/sourcelair/xterm.js.svg)](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/xterm/badge?style=rounded)](https://www.jsdelivr.com/package/npm/xterm) Xterm.js is a terminal front-end component written in JavaScript that works in the browser. @@ -115,6 +115,11 @@ computational environment for Jupyter, supporting interactive data science and s - [**Theia**](https://github.com/theia-ide/theia): Theia is a cloud & desktop IDE framework implemented in TypeScript. - [**Opshell**](https://github.com/ricktbaker/opshell) Ops Helper tool to make life easier working with AWS instances across multiple organizations. - [**Proxmox VE**](https://www.proxmox.com/en/proxmox-ve): Proxmox VE is a complete open-source platform for enterprise virtualization. It uses xterm.js for container terminals and the host shell. +- [**Script Runner**](https://github.com/ioquatix/script-runner): Run scripts (or a shell) in Atom. +- [**Whack Whack Terminal**](https://github.com/Microsoft/WhackWhackTerminal): Terminal emulator for Visual Studio 2017. +- [**VTerm**](https://github.com/vterm/vterm): Extensible terminal emulator based on Electron and React. +- [**electerm**](http://electerm.html5beta.com): electerm is a terminal/ssh/sftp client(mac, win, linux) based on electron/node-pty/xterm. +- [**Kubebox**](https://github.com/astefanutti/kubebox): Terminal console for Kubernetes clusters. Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list. diff --git a/package.json b/package.json index e4bb08c1..1dfadf9f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xterm", "description": "Full xterm terminal, in your browser", - "version": "2.9.1", + "version": "3.0.0", "ignore": [ "demo", "test", @@ -9,7 +9,7 @@ ], "main": "lib/Terminal.js", "types": "typings/xterm.d.ts", - "repository": "https://github.com/sourcelair/xterm.js", + "repository": "https://github.com/xtermjs/xterm.js", "license": "MIT", "files": [ "*.js", @@ -35,7 +35,8 @@ "src/*.ts", "src/**/*.js", "src/**/*.js.map", - "src/**/*.ts" + "src/**/*.ts", + "typings/*.d.ts" ], "devDependencies": { "@types/chai": "^3.4.34", diff --git a/src/Parser.ts b/src/Parser.ts index b6493fea..6f4b0932 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -286,36 +286,43 @@ export class Parser { // Single Shift Select of G2 Character Set // ( SS2 is 0x8e). This affects next character only. case 'N': + this._state = ParserState.NORMAL; break; // ESC O // Single Shift Select of G3 Character Set // ( SS3 is 0x8f). This affects next character only. case 'O': + this._state = ParserState.NORMAL; break; // ESC n // Invoke the G2 Character Set as GL (LS2). case 'n': this._terminal.setgLevel(2); + this._state = ParserState.NORMAL; break; // ESC o // Invoke the G3 Character Set as GL (LS3). case 'o': this._terminal.setgLevel(3); + this._state = ParserState.NORMAL; break; // ESC | // Invoke the G3 Character Set as GR (LS3R). case '|': this._terminal.setgLevel(3); + this._state = ParserState.NORMAL; break; // ESC } // Invoke the G2 Character Set as GR (LS2R). case '}': this._terminal.setgLevel(2); + this._state = ParserState.NORMAL; break; // ESC ~ // Invoke the G1 Character Set as GR (LS1R). case '~': this._terminal.setgLevel(1); + this._state = ParserState.NORMAL; break; // ESC 7 Save Cursor (DECSC). diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 8c6ea3e1..a10dc669 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -14,6 +14,10 @@ import { BufferSet } from './BufferSet'; import { MockTerminal } from './utils/TestUtils.test'; import { LineData, CharData } from './Types'; +class TestMockTerminal extends MockTerminal { + emit(event: string, data: any): void {} +} + class TestSelectionManager extends SelectionManager { constructor( terminal: ITerminal, @@ -48,7 +52,7 @@ describe('SelectionManager', () => { dom = new jsdom.JSDOM(''); window = dom.window; document = window.document; - terminal = new MockTerminal(); + terminal = new TestMockTerminal(); terminal.cols = 80; terminal.rows = 2; terminal.options.scrollback = 100; diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 387ce8d1..cd6fc996 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -255,7 +255,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager public selectAll(): void { this._model.isSelectAllActive = true; this.refresh(); - this.emit('selection'); + this._terminal.emit('selection'); } /** @@ -528,7 +528,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager this._terminal.scrollLines(this._dragScrollAmount, false); // Re-evaluate selection if (this._dragScrollAmount > 0) { - this._model.selectionEnd = [this._terminal.cols - 1, this._terminal.buffer.ydisp + this._terminal.rows]; + this._model.selectionEnd = [this._terminal.cols - 1, Math.min(this._terminal.buffer.ydisp + this._terminal.rows, this._terminal.buffer.lines.length - 1)]; } else { this._model.selectionEnd = [0, this._terminal.buffer.ydisp]; } @@ -544,7 +544,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager this._removeMouseDownListeners(); if (this.hasSelection) - this.emit('selection'); + this._terminal.emit('selection'); } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index dd2bf405..f95cc0ea 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1448,6 +1448,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT if (ev.shiftKey) { result.key = C0.BS; // ^H break; + } else if (ev.altKey) { + result.key = C0.ESC + C0.DEL; // \e ^? + break; } result.key = C0.DEL; // ^? break; diff --git a/src/addons/attach/attach.ts b/src/addons/attach/attach.ts index aade7291..4e0d5941 100644 --- a/src/addons/attach/attach.ts +++ b/src/addons/attach/attach.ts @@ -59,6 +59,9 @@ export function attach(term, socket, bidirectional, buffered) { }; term._sendData = function(data) { + if (socket.readyState !== 1) { + return; + } socket.send(data); }; diff --git a/src/addons/fit/fit.ts b/src/addons/fit/fit.ts index f8e02015..b87ccc07 100644 --- a/src/addons/fit/fit.ts +++ b/src/addons/fit/fit.ts @@ -34,7 +34,7 @@ export function proposeGeometry(term) { }; export function fit(term) { - var geometry = exports.proposeGeometry(term); + var geometry = proposeGeometry(term); if (geometry) { // Force a full render if (term.rows !== geometry.rows || term.cols !== geometry.cols) { diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 422d424c..31547da4 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -23,6 +23,8 @@ export class Renderer extends EventEmitter implements IRenderer { private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; private _screenDprMonitor: ScreenDprMonitor; + private _isPaused: boolean = false; + private _needsFullRefresh: boolean = false; public colorManager: ColorManager; public dimensions: IRenderDimensions; @@ -58,6 +60,20 @@ export class Renderer extends EventEmitter implements IRenderer { this._screenDprMonitor = new ScreenDprMonitor(); this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio)); + + // Detect whether IntersectionObserver is detected and enable renderer pause + // and resume based on terminal visibility if so + if ('IntersectionObserver' in window) { + const observer = new IntersectionObserver(e => this.onIntersectionChange(e[0]), {threshold: 0}); + observer.observe(this._terminal.element); + } + } + + public onIntersectionChange(entry: IntersectionObserverEntry): void { + this._isPaused = entry.intersectionRatio === 0; + if (!this._isPaused && this._needsFullRefresh) { + this._terminal.refresh(0, this._terminal.rows - 1); + } } public onWindowResize(devicePixelRatio: number): void { @@ -78,7 +94,11 @@ export class Renderer extends EventEmitter implements IRenderer { l.reset(this._terminal); }); - this._terminal.refresh(0, this._terminal.rows - 1); + if (this._isPaused) { + this._needsFullRefresh = true; + } else { + this._terminal.refresh(0, this._terminal.rows - 1); + } return this.colorManager.colors; } @@ -91,7 +111,11 @@ export class Renderer extends EventEmitter implements IRenderer { this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions, didCharSizeChange)); // Force a refresh - this._terminal.refresh(0, this._terminal.rows - 1); + if (this._isPaused) { + this._needsFullRefresh = true; + } else { + this._terminal.refresh(0, this._terminal.rows - 1); + } this.emit('resize', { width: this.dimensions.canvasWidth, @@ -104,27 +128,35 @@ export class Renderer extends EventEmitter implements IRenderer { } public onBlur(): void { - this._renderLayers.forEach(l => l.onBlur(this._terminal)); + this._runOperation(l => l.onBlur(this._terminal)); } public onFocus(): void { - this._renderLayers.forEach(l => l.onFocus(this._terminal)); + this._runOperation(l => l.onFocus(this._terminal)); } public onSelectionChanged(start: [number, number], end: [number, number]): void { - this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end)); + this._runOperation(l => l.onSelectionChanged(this._terminal, start, end)); } public onCursorMove(): void { - this._renderLayers.forEach(l => l.onCursorMove(this._terminal)); + this._runOperation(l => l.onCursorMove(this._terminal)); } public onOptionsChanged(): void { - this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal)); + this._runOperation(l => l.onOptionsChanged(this._terminal)); } public clear(): void { - this._renderLayers.forEach(l => l.reset(this._terminal)); + this._runOperation(l => l.reset(this._terminal)); + } + + private _runOperation(operation: (layer: IRenderLayer) => void): void { + if (this._isPaused) { + this._needsFullRefresh = true; + } else { + this._renderLayers.forEach(l => operation(l)); + } } /** @@ -134,6 +166,10 @@ export class Renderer extends EventEmitter implements IRenderer { * @param {number} end The end row. */ public queueRefresh(start: number, end: number): void { + if (this._isPaused) { + this._needsFullRefresh = true; + return; + } this._refreshRowsQueue.push({ start: start, end: end }); if (!this._refreshAnimationFrame) { this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this)); diff --git a/src/xterm.css b/src/xterm.css index 35e92f63..b3ee50f0 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -95,6 +95,7 @@ /* On OS X this is required in order for the scroll bar to appear fully opaque */ background-color: #000; overflow-y: scroll; + cursor: default; } .xterm canvas {