diff --git a/README.md b/README.md index bde713a7..512f4dce 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # [![xterm.js logo](logo-full.png)](https://xtermjs.org) -[![Travis CI build status](https://api.travis-ci.org/xtermjs/xterm.js.svg)](https://travis-ci.org/xtermjs/xterm.js) [![VSTS Build status](https://xtermjs.visualstudio.com/_apis/public/build/definitions/3e323cf7-5760-460d-af64-ee5675baf366/1/badge)](https://xtermjs.visualstudio.com/xterm.js/_build/index?definitionId=1) [![Coverage Status](https://coveralls.io/repos/github/xtermjs/xterm.js/badge.svg?branch=master)](https://coveralls.io/github/xtermjs/xterm.js?branch=master) [![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) +[![VSTS Build status](https://xtermjs.visualstudio.com/_apis/public/build/definitions/3e323cf7-5760-460d-af64-ee5675baf366/1/badge)](https://xtermjs.visualstudio.com/xterm.js/_build/index?definitionId=1) +[![Coverage Status](https://coveralls.io/repos/github/xtermjs/xterm.js/badge.svg?branch=master)](https://coveralls.io/github/xtermjs/xterm.js?branch=master) +[![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. @@ -57,6 +60,12 @@ The proposed way to load xterm.js is via the ES6 module syntax. import { Terminal } from 'xterm'; ``` +### API + +The full API for xterm.js is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API. + +Note that some APIs are marked *experimental*, these are added so we can experiment with new ideas without committing to support it like a normal semver API. Note that these APIs can change radically between versions so be sure to read release notes if you plan on using experimental APIs. + ### Addons Addons are JavaScript modules that extend the `Terminal` prototype with new methods and attributes to provide additional functionality. There are a handful available in the main repository in the `src/addons` directory and you can even write your own, by using xterm.js' public API. @@ -74,6 +83,8 @@ var xterm = new Terminal(); // Instantiate the terminal xterm.fit(); // Use the `fit` method, provided by the `fit` addon ``` +You will also need to include the addon's CSS file if it has one in the folder. + #### Importing Addons in TypeScript There are currently no typings for addons if they are accessed via extending Terminal prototype, so you will need to upcast if using TypeScript, eg. `(xterm).fit()`. @@ -107,6 +118,10 @@ Since xterm.js is typically implemented as a developer tool, only modern browser Xterm.js works seamlessly in Electron apps and may even work on earlier versions of the browsers but these are the browsers we strive to keep working. +## API + +The current full API documentation is available in the [TypeScript declaration file on the repository](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), switch the tag (press `w` when viewing the file) to point at the specific version tag you're using. + ## Real-world uses Xterm.js is used in several world-class applications to provide great terminal experiences. @@ -155,6 +170,8 @@ computational environment for Jupyter, supporting interactive data science and s - [**Nutanix**](https://github.com/nutanix): Nutanix Enterprise Cloud uses xterm in the webssh functionality within Nutanix Calm, and is also looking to move our old noserial (termjs) functionality to xterm.js - [**SSH Web Client**](https://github.com/roke22/PHP-SSH2-Web-Client): SSH Web Client with PHP. +[And much more...](https://github.com/xtermjs/xterm.js/network/dependents) + 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. Note: Please add any new contributions to the end of the list only. ## Releases diff --git a/demo/main.js b/demo/main.js index a9b11655..70544e38 100644 --- a/demo/main.js +++ b/demo/main.js @@ -174,9 +174,7 @@ function initOptions(term) { 'termName', 'useFlowControl', // Complex option - 'theme', - // Only in constructor - 'rendererType' + 'theme' ]; var stringOptions = { bellSound: null, @@ -185,7 +183,8 @@ function initOptions(term) { experimentalCharAtlas: ['none', 'static', 'dynamic'], fontFamily: null, fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], - fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'] + fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], + rendererType: ['dom', 'canvas'] }; var options = Object.keys(term._core.options); var booleanOptions = []; diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 0975e0b6..1a2de1d7 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -72,7 +72,7 @@ export class AccessibilityManager extends Disposable { this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot); this.register(this._renderRowsDebouncer); - this.register(this._terminal.addDisposableListener('resize', data => this._onResize(data.cols, data.rows))); + this.register(this._terminal.addDisposableListener('resize', data => this._onResize(data.rows))); this.register(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end))); this.register(this._terminal.addDisposableListener('scroll', data => this._refreshRows())); // Line feed is an issue as the prompt won't be read out after a command is run @@ -157,7 +157,7 @@ export class AccessibilityManager extends Disposable { e.stopImmediatePropagation(); } - private _onResize(cols: number, rows: number): void { + private _onResize(rows: number): void { // Remove bottom boundary listener this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener); @@ -259,6 +259,9 @@ export class AccessibilityManager extends Disposable { if (!this._terminal.renderer.dimensions.actualCellHeight) { return; } + if (this._rowElements.length !== this._terminal.rows) { + this._onResize(this._terminal.rows); + } for (let i = 0; i < this._terminal.rows; i++) { this._refreshRowDimensions(this._rowElements[i]); } diff --git a/src/Buffer.ts b/src/Buffer.ts index 5183009f..5d45645f 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -15,6 +15,10 @@ export const CHAR_DATA_WIDTH_INDEX = 2; export const CHAR_DATA_CODE_INDEX = 3; export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 +export const NULL_CELL_CHAR = ' '; +export const NULL_CELL_WIDTH = 1; +export const NULL_CELL_CODE = 32; + /** * This class represents a terminal buffer (an internal state of the terminal), where the * following information is stored (in high-level): @@ -117,7 +121,7 @@ export class Buffer implements IBuffer { if (this.lines.length > 0) { // Deal with columns increasing (we don't do anything when columns reduce) if (this._terminal.cols < newCols) { - const ch: CharData = [DEFAULT_ATTR, ' ', 1, 32]; // does xterm use the default attr? + const ch: CharData = [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // does xterm use the default attr? for (let i = 0; i < this.lines.length; i++) { while (this.lines.get(i).length < newCols) { this.lines.get(i).push(ch); diff --git a/src/BufferSet.ts b/src/BufferSet.ts index 553b2056..4b5ca2d1 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -18,7 +18,7 @@ export class BufferSet extends EventEmitter implements IBufferSet { /** * Create a new BufferSet for the given terminal. - * @param {Terminal} terminal - The terminal the BufferSet will belong to + * @param _terminal - The terminal the BufferSet will belong to */ constructor(private _terminal: ITerminal) { super(); @@ -35,7 +35,6 @@ export class BufferSet extends EventEmitter implements IBufferSet { /** * Returns the alt Buffer of the BufferSet - * @returns {Buffer} */ public get alt(): Buffer { return this._alt; @@ -43,7 +42,6 @@ export class BufferSet extends EventEmitter implements IBufferSet { /** * Returns the normal Buffer of the BufferSet - * @returns {Buffer} */ public get active(): Buffer { return this._activeBuffer; @@ -51,7 +49,6 @@ export class BufferSet extends EventEmitter implements IBufferSet { /** * Returns the currently active Buffer of the BufferSet - * @returns {Buffer} */ public get normal(): Buffer { return this._normal; diff --git a/src/CompositionHelper.ts b/src/CompositionHelper.ts index b721b7f1..b1745d41 100644 --- a/src/CompositionHelper.ts +++ b/src/CompositionHelper.ts @@ -61,7 +61,7 @@ export class CompositionHelper { /** * Handles the compositionupdate event, updating the composition view. - * @param {CompositionEvent} ev The event. + * @param ev The event. */ public compositionupdate(ev: CompositionEvent): void { this._compositionView.textContent = ev.data; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 4def4f75..c1e05d53 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,7 +7,7 @@ import { CharData, IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer } from './Types'; import { C0, C1 } from './common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets'; -import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, DEFAULT_ATTR } from './Buffer'; +import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer'; import { FLAGS } from './renderer/Types'; import { wcwidth } from './CharWidth'; import { EscapeSequenceParser } from './EscapeSequenceParser'; @@ -436,11 +436,11 @@ export class InputHandler extends Disposable implements IInputHandler { if (removed[CHAR_DATA_WIDTH_INDEX] === 0 && bufferRow[this._terminal.cols - 2] && bufferRow[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) { - bufferRow[this._terminal.cols - 2] = [curAttr, ' ', 1, 32 /* ' '.charCodeAt(0) */ ]; + bufferRow[this._terminal.cols - 2] = [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; } // insert empty cell at cursor - bufferRow.splice(buffer.x, 0, [curAttr, ' ', 1, 32 /* ' '.charCodeAt(0) */ ]); + bufferRow.splice(buffer.x, 0, [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); } } @@ -552,7 +552,7 @@ export class InputHandler extends Disposable implements IInputHandler { const row = buffer.y + buffer.ybase; let j = buffer.x; - const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm + const ch: CharData = [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm while (param-- && j < this._terminal.cols) { buffer.lines.get(row).splice(j++, 0, ch); @@ -862,7 +862,7 @@ export class InputHandler extends Disposable implements IInputHandler { const buffer = this._terminal.buffer; const row = buffer.y + buffer.ybase; - const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm + const ch: CharData = [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm while (param--) { buffer.lines.get(row).splice(buffer.x, 1); @@ -924,7 +924,7 @@ export class InputHandler extends Disposable implements IInputHandler { const row = buffer.y + buffer.ybase; let j = buffer.x; - const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm + const ch: CharData = [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm while (param-- && j < this._terminal.cols) { buffer.lines.get(row)[j++] = ch; @@ -986,7 +986,7 @@ export class InputHandler extends Disposable implements IInputHandler { const buffer = this._terminal.buffer; const line = buffer.lines.get(buffer.ybase + buffer.y); - const ch = line[buffer.x - 1] || [DEFAULT_ATTR, ' ', 1, 32]; + const ch = line[buffer.x - 1] || [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; while (param--) { line[buffer.x++] = ch; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 4ae0c08f..20e1ca60 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -183,6 +183,42 @@ describe('SelectionManager', () => { selectionManager.selectWordAt([15, 0]); assert.equal(selectionManager.selectionText, 'ij"'); }); + it('should expand upwards or downards for wrapped lines', () => { + buffer.lines.set(0, stringToRow(' foo')); + buffer.lines.set(1, stringToRow('bar ')); + (buffer.lines.get(1)).isWrapped = true; + selectionManager.selectWordAt([1, 1]); + assert.equal(selectionManager.selectionText, 'foobar'); + selectionManager.model.clearSelection(); + selectionManager.selectWordAt([78, 0]); + assert.equal(selectionManager.selectionText, 'foobar'); + }); + it('should expand both upwards and downwards for word wrapped over many lines', () => { + const expectedText = 'fooaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccbar'; + buffer.lines.set(0, stringToRow(' foo')); + buffer.lines.set(1, stringToRow('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')); + buffer.lines.set(2, stringToRow('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')); + buffer.lines.set(3, stringToRow('cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc')); + buffer.lines.set(4, stringToRow('bar ')); + (buffer.lines.get(1)).isWrapped = true; + (buffer.lines.get(2)).isWrapped = true; + (buffer.lines.get(3)).isWrapped = true; + (buffer.lines.get(4)).isWrapped = true; + selectionManager.selectWordAt([78, 0]); + assert.equal(selectionManager.selectionText, expectedText); + selectionManager.model.clearSelection(); + selectionManager.selectWordAt([40, 1]); + assert.equal(selectionManager.selectionText, expectedText); + selectionManager.model.clearSelection(); + selectionManager.selectWordAt([40, 2]); + assert.equal(selectionManager.selectionText, expectedText); + selectionManager.model.clearSelection(); + selectionManager.selectWordAt([40, 3]); + assert.equal(selectionManager.selectionText, expectedText); + selectionManager.model.clearSelection(); + selectionManager.selectWordAt([1, 4]); + assert.equal(selectionManager.selectionText, expectedText); + }); describe('emoji', () => { it('should treat a single emoji as a word when wrapped in spaces', () => { buffer.lines.set(0, stringToRow(' ⚽ a')); // The a is here to prevent the space being trimmed in selectionText diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 1de14e70..3731eb9a 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -9,7 +9,7 @@ import * as Browser from './shared/utils/Browser'; import { CharMeasure } from './ui/CharMeasure'; import { EventEmitter } from './EventEmitter'; import { SelectionModel } from './SelectionModel'; -import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer'; +import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX } from './Buffer'; import { AltClickHandler } from './handlers/AltClickHandler'; /** @@ -451,8 +451,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * Removes the listeners that are registered when mousedown is triggered. */ private _removeMouseDownListeners(): void { - this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); - this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); + if (this._terminal.element.ownerDocument) { + this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); + } clearInterval(this._dragScrollIntervalTimer); this._dragScrollIntervalTimer = null; } @@ -677,7 +679,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * Gets positional information for the word at the coordinated specified. * @param coords The coordinates to get the word at. */ - private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): IWordPosition { + private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean, followWrappedLinesAbove: boolean = true, followWrappedLinesBelow: boolean = true): IWordPosition { // Ensure coords are within viewport (eg. not within scroll bar) if (coords[0] >= this._terminal.cols) { return null; @@ -772,7 +774,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // Calculate the start _column_, converting the the string indexes back to // column coordinates. - const start = + let start = startIndex // The index of the selection's start char in the line string + charOffset // The difference between the initial char's column and index - leftWideCharCount // The number of wide chars left of the initial char @@ -780,7 +782,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // Calculate the length in _columns_, converting the the string indexes back // to column coordinates. - const length = Math.min(this._terminal.cols, // Disallow lengths larger than the terminal cols + let length = Math.min(this._terminal.cols, // Disallow lengths larger than the terminal cols endIndex // The index of the selection's end char in the line string - startIndex // The index of the selection's start char in the line string + leftWideCharCount // The number of wide chars left of the initial char @@ -792,6 +794,34 @@ export class SelectionManager extends EventEmitter implements ISelectionManager return null; } + // Recurse upwards if the line is wrapped and the word wraps to the above line + if (followWrappedLinesAbove) { + if (start === 0 && bufferLine[0][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { + const previousBufferLine = this._buffer.lines.get(coords[1] - 1); + if (previousBufferLine && (bufferLine).isWrapped && previousBufferLine[this._terminal.cols - 1][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { + const previousLineWordPosition = this._getWordAt([this._terminal.cols - 1, coords[1] - 1], false, true, false); + if (previousLineWordPosition) { + const offset = this._terminal.cols - previousLineWordPosition.start; + start -= offset; + length += offset; + } + } + } + } + + // Recurse downwards if the line is wrapped and the word wraps to the next line + if (followWrappedLinesBelow) { + if (start + length === this._terminal.cols && bufferLine[this._terminal.cols - 1][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { + const nextBufferLine = this._buffer.lines.get(coords[1] + 1); + if (nextBufferLine && (nextBufferLine).isWrapped && nextBufferLine[0][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { + const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true); + if (nextLineWordPosition) { + length += nextLineWordPosition.length; + } + } + } + } + return { start, length }; } @@ -803,6 +833,11 @@ export class SelectionManager extends EventEmitter implements ISelectionManager protected _selectWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): void { const wordPosition = this._getWordAt(coords, allowWhitespaceOnlySelection); if (wordPosition) { + // Adjust negative start value + while (wordPosition.start < 0) { + wordPosition.start += this._terminal.cols; + coords[1]--; + } this._model.selectionStart = [wordPosition.start, coords[1]]; this._model.selectionStartLength = wordPosition.length; } @@ -815,7 +850,24 @@ export class SelectionManager extends EventEmitter implements ISelectionManager private _selectToWordAt(coords: [number, number]): void { const wordPosition = this._getWordAt(coords, true); if (wordPosition) { - this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]]; + let endRow = coords[1]; + + // Adjust negative start value + while (wordPosition.start < 0) { + wordPosition.start += this._terminal.cols; + endRow--; + } + + // Adjust wrapped length value, this only needs to happen when values are reversed as in that + // case we're interested in the start of the word, not the end + if (!this._model.areSelectionValuesReversed()) { + while (wordPosition.start + wordPosition.length > this._terminal.cols) { + wordPosition.length -= this._terminal.cols; + endRow++; + } + } + + this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : wordPosition.start + wordPosition.length, endRow]; } } diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index 85486bab..59b2ce75 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -128,6 +128,11 @@ describe('SelectionManager', () => { model.selectionEnd = [3, 2]; assert.deepEqual(model.finalSelectionEnd, [4, 2]); }); + it('should return the end on a different row when start + length overflows onto a following row', () => { + model.selectionStart = [78, 2]; + model.selectionStartLength = 4; + assert.deepEqual(model.finalSelectionEnd, [2, 3]); + }); it('should return selection end if selection end is after selection start + length', () => { model.selectionStart = [2, 2]; model.selectionStartLength = 2; diff --git a/src/SelectionModel.ts b/src/SelectionModel.ts index a9a3c89e..f87667f2 100644 --- a/src/SelectionModel.ts +++ b/src/SelectionModel.ts @@ -76,9 +76,13 @@ export class SelectionModel { return null; } - // Use the selection start if the end doesn't exist or they're reversed + // Use the selection start + length if the end doesn't exist or they're reversed if (!this.selectionEnd || this.areSelectionValuesReversed()) { - return [this.selectionStart[0] + this.selectionStartLength, this.selectionStart[1]]; + const startPlusLength = this.selectionStart[0] + this.selectionStartLength; + if (startPlusLength > this._terminal.cols) { + return [startPlusLength % this._terminal.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._terminal.cols)]; + } + return [startPlusLength, this.selectionStart[1]]; } // Ensure the the word/line is selected after a double/triple click diff --git a/src/Terminal.ts b/src/Terminal.ts index 2d247080..de753431 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -25,7 +25,7 @@ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions import { IMouseZoneManager } from './ui/Types'; import { IRenderer } from './renderer/Types'; import { BufferSet } from './BufferSet'; -import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR } from './Buffer'; +import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer'; import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from './EventEmitter'; import { Viewport } from './Viewport'; @@ -72,7 +72,7 @@ const WRITE_BATCH_SIZE = 300; /** * The set of options that only have an effect when set in the Terminal constructor. */ -const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows', 'rendererType']; +const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows']; const DEFAULT_OPTIONS: ITerminalOptions = { cols: 80, @@ -208,6 +208,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public mouseHelper: MouseHelper; private _accessibilityManager: AccessibilityManager; private _screenDprMonitor: ScreenDprMonitor; + private _theme: ITheme; public cols: number; public rows: number; @@ -215,7 +216,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Creates a new `Terminal` object. * - * @param {object} options An object containing a set of options, the available options are: + * @param options An object containing a set of options, the available options are: * - `cursorBlink` (boolean): Whether the terminal cursor blinks * - `cols` (number): The number of columns of the terminal (horizontal size) * - `rows` (number): The number of rows of the terminal (vertical size) @@ -350,7 +351,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Retrieves an option's value from the terminal. - * @param {string} key The option key. + * @param key The option key. */ public getOption(key: string): any { if (!(key in DEFAULT_OPTIONS)) { @@ -362,8 +363,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Sets an option on the terminal. - * @param {string} key The option key. - * @param {any} value The option value. + * @param key The option key. + * @param value The option value. */ public setOption(key: string, value: any): void { if (!(key in DEFAULT_OPTIONS)) { @@ -372,6 +373,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (CONSTRUCTOR_ONLY_OPTIONS.indexOf(key) !== -1) { console.error(`Option "${key}" can only be set in the constructor`); } + if (this.options[key] === value) { + return; + } switch (key) { case 'bellStyle': if (!value) { @@ -398,6 +402,11 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II console.warn(`${key} cannot be less than 1, value: ${value}`); return; } + case 'rendererType': + if (!value) { + value = 'canvas'; + } + break; case 'tabStopWidth': if (value < 1) { console.warn(`${key} cannot be less than 1, value: ${value}`); @@ -457,6 +466,18 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.renderer.onResize(this.cols, this.rows); this.refresh(0, this.rows - 1); } + case 'rendererType': + if (this.renderer) { + this.unregister(this.renderer); + this.renderer.dispose(); + this.renderer = null; + } + this._setupRenderer(); + this.renderer.onCharSizeChanged(); + if (this._theme) { + this.renderer.setTheme(this._theme); + } + break; case 'scrollback': this.buffers.resize(this.cols, this.rows); if (this.viewport) { @@ -604,7 +625,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Opens the terminal within an element. * - * @param {HTMLElement} parent The element to create the terminal within. + * @param parent The element to create the terminal within. */ public open(parent: HTMLElement): void { this._parent = parent || this._parent; @@ -676,12 +697,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // Performance: Add viewport and helper elements from the fragment this.element.appendChild(fragment); - switch (this.options.rendererType) { - case 'canvas': this.renderer = new Renderer(this, this.options.theme); break; - case 'dom': this.renderer = new DomRenderer(this, this.options.theme); break; - default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); - } - this.register(this.renderer); + this._setupRenderer(); + this._theme = this.options.theme; this.options.theme = null; this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure); this.viewport.onThemeChanged(this.renderer.colorManager.colors); @@ -738,11 +755,21 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } + private _setupRenderer(): void { + switch (this.options.rendererType) { + case 'canvas': this.renderer = new Renderer(this, this.options.theme); break; + case 'dom': this.renderer = new DomRenderer(this, this.options.theme); break; + default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); + } + this.register(this.renderer); + } + /** * Sets the theme on the renderer. The renderer must have been initialized. - * @param theme The theme to ste. + * @param theme The theme to set. */ private _setTheme(theme: ITheme): void { + this._theme = theme; const colors = this.renderer.setTheme(theme); if (this.viewport) { this.viewport.onThemeChanged(colors); @@ -1100,8 +1127,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Tells the renderer to refresh terminal content between two rows (inclusive) at the next * opportunity. - * @param {number} start The row to start from (between 0 and this.rows - 1). - * @param {number} end The row to end at (between start and this.rows - 1). + * @param start The row to start from (between 0 and this.rows - 1). + * @param end The row to end at (between start and this.rows - 1). */ public refresh(start: number, end: number): void { if (this.renderer) { @@ -1111,8 +1138,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Queues linkification for the specified rows. - * @param {number} start The row to start from (between 0 and this.rows - 1). - * @param {number} end The row to end at (between start and this.rows - 1). + * @param start The row to start from (between 0 and this.rows - 1). + * @param end The row to end at (between start and this.rows - 1). */ private _queueLinkification(start: number, end: number): void { if (this.linkifier) { @@ -1204,8 +1231,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Scroll the display of the terminal - * @param {number} disp The number of lines to scroll down (negative scroll up). - * @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollLines. This is used + * @param disp The number of lines to scroll down (negative scroll up). + * @param suppressScrollEvent Don't emit the scroll event as scrollLines. This is used * to avoid unwanted events being handled by the viewport when the event was triggered from the * viewport originally. */ @@ -1236,7 +1263,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Scroll the display of the terminal by a number of pages. - * @param {number} pageCount The number of pages to scroll (negative scrolls up). + * @param pageCount The number of pages to scroll (negative scrolls up). */ public scrollPages(pageCount: number): void { this.scrollLines(pageCount * (this.rows - 1)); @@ -1265,7 +1292,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Writes text to the terminal. - * @param {string} data The text to write to the terminal. + * @param data The text to write to the terminal. */ public write(data: string): void { // Ensure the terminal isn't disposed @@ -1341,7 +1368,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Writes text to the terminal, followed by a break line character (\n). - * @param {string} data The text to write to the terminal. + * @param data The text to write to the terminal. */ public writeln(data: string): void { this.write(data + '\r\n'); @@ -1454,7 +1481,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * Handle a keydown event * Key Resources: * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent - * @param {KeyboardEvent} ev The keydown event to be handled. + * @param ev The keydown event to be handled. */ protected _keyDown(event: KeyboardEvent): boolean { if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) { @@ -1551,7 +1578,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * Handle a keypress event. * Key Resources: * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent - * @param {KeyboardEvent} ev The keypress event to be handled. + * @param ev The keypress event to be handled. */ protected _keyPress(ev: KeyboardEvent): boolean { let key; @@ -1590,7 +1617,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Send data for handling to the terminal - * @param {string} data */ public send(data: string): void { if (!this._sendDataQueue) { @@ -1643,8 +1669,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Resizes the terminal. * - * @param {number} x The number of columns to resize to. - * @param {number} y The number of rows to resize to. + * @param x The number of columns to resize to. + * @param y The number of rows to resize to. */ public resize(x: number, y: number): void { if (isNaN(x) || isNaN(y)) { @@ -1678,7 +1704,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Updates the range of rows to refresh - * @param {number} y The number of rows to refresh next. + * @param y The number of rows to refresh next. */ public updateRange(y: number): void { if (y < this._refreshStart) this._refreshStart = y; @@ -1701,15 +1727,15 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Erase in the identified line everything from "x" to the end of the line (right). - * @param {number} x The column from which to start erasing to the end of the line. - * @param {number} y The line in which to operate. + * @param x The column from which to start erasing to the end of the line. + * @param y The line in which to operate. */ public eraseRight(x: number, y: number): void { const line = this.buffer.lines.get(this.buffer.ybase + y); if (!line) { return; } - const ch: CharData = [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */]; // xterm + const ch: CharData = [this.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm for (; x < this.cols; x++) { line[x] = ch; } @@ -1718,15 +1744,15 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Erase in the identified line everything from "x" to the start of the line (left). - * @param {number} x The column from which to start erasing to the start of the line. - * @param {number} y The line in which to operate. + * @param x The column from which to start erasing to the start of the line. + * @param y The line in which to operate. */ public eraseLeft(x: number, y: number): void { const line = this.buffer.lines.get(this.buffer.ybase + y); if (!line) { return; } - const ch: CharData = [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */]; // xterm + const ch: CharData = [this.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm x++; while (x--) { line[x] = ch; @@ -1756,7 +1782,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Erase all content in the given line - * @param {number} y The line to erase all of its contents. + * @param y The line to erase all of its contents. */ public eraseLine(y: number): void { this.eraseRight(0, y); @@ -1764,15 +1790,15 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Return the data array of a blank line - * @param {boolean} cur First bunch of data for each "blank" character. - * @param {boolean} isWrapped Whether the new line is wrapped from the previous line. - * @param {boolean} cols The number of columns in the terminal, if this is not + * @param cur First bunch of data for each "blank" character. + * @param isWrapped Whether the new line is wrapped from the previous line. + * @param cols The number of columns in the terminal, if this is not * set, the terminal's current column count would be used. */ public blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData { const attr = cur ? this.eraseAttr() : DEFAULT_ATTR; - const ch: CharData = [attr, ' ', 1, 32 /* ' '.charCodeAt(0) */]; // width defaults to 1 halfwidth character + const ch: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // width defaults to 1 halfwidth character const line: LineData = []; // TODO: It is not ideal that this is a property on an array, a buffer line @@ -1795,9 +1821,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II */ public ch(cur?: boolean): CharData { if (cur) { - return [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */]; + return [this.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; } - return [DEFAULT_ATTR, ' ', 1, 32 /* ' '.charCodeAt(0) */]; + return [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; } /** @@ -1810,7 +1836,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Emit the 'data' event and populate the given data. - * @param {string} data The data to populate in the event. + * @param data The data to populate in the event. */ public handler(data: string): void { // Prevents all events to pty process if stdin is disabled @@ -1832,7 +1858,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Emit the 'title' event and populate the given title. - * @param {string} title The title to populate in the event. + * @param title The title to populate in the event. */ public handleTitle(title: string): void { /** diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts index 84b21590..25ad7d91 100644 --- a/src/addons/winptyCompat/winptyCompat.ts +++ b/src/addons/winptyCompat/winptyCompat.ts @@ -6,6 +6,9 @@ import { Terminal } from 'xterm'; import { IWinptyCompatAddonTerminal } from './Interfaces'; +const CHAR_DATA_CODE_INDEX = 3; +const NULL_CELL_CODE = 32; + export function winptyCompatInit(terminal: Terminal): void { const addonTerminal = terminal; @@ -29,7 +32,7 @@ export function winptyCompatInit(terminal: Terminal): void { const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1); const lastChar = line[addonTerminal.cols - 1]; - if (lastChar[3] !== 32 /* ' ' */) { + if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE) { const nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y); (nextLine).isWrapped = true; } diff --git a/src/common/Lifecycle.ts b/src/common/Lifecycle.ts index b9cf1418..46828521 100644 --- a/src/common/Lifecycle.ts +++ b/src/common/Lifecycle.ts @@ -30,4 +30,16 @@ export abstract class Disposable implements IDisposable { public register(d: T): void { this._disposables.push(d); } + + /** + * Unregisters a disposable object if it has been registered, if not do + * nothing. + * @param d The disposable to unregister. + */ + public unregister(d: T): void { + const index = this._disposables.indexOf(d); + if (index !== -1) { + this._disposables.splice(index, 1); + } + } } diff --git a/src/handlers/Clipboard.ts b/src/handlers/Clipboard.ts index bbb8302c..b1acba9d 100644 --- a/src/handlers/Clipboard.ts +++ b/src/handlers/Clipboard.ts @@ -35,7 +35,7 @@ export function bracketTextForPaste(text: string, bracketedPasteMode: boolean): /** * Binds copy functionality to the given terminal. - * @param {ClipboardEvent} ev The original copy event to be handled + * @param ev The original copy event to be handled */ export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager): void { if (term.browser.isMSIE) { @@ -50,8 +50,8 @@ export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManage /** * Redirect the clipboard's data to the terminal's input handler. - * @param {ClipboardEvent} ev The original paste event to be handled - * @param {Terminal} term The terminal on which to apply the handled paste event + * @param ev The original paste event to be handled + * @param term The terminal on which to apply the handled paste event */ export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void { ev.stopPropagation(); diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 111e63ff..19c23dc0 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -36,6 +36,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._container.appendChild(this._canvas); } + public dispose(): void { + this._container.removeChild(this._canvas); + } + private _initCanvas(): void { this._ctx = this._canvas.getContext('2d', {alpha: this._alpha}); // Draw the background if this is an opaque layer diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 406717b5..a6189efc 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -76,6 +76,11 @@ export class Renderer extends EventEmitter implements IRenderer { } } + public dispose(): void { + super.dispose(); + this._renderLayers.forEach(l => l.dispose()); + } + public onIntersectionChange(entry: IntersectionObserverEntry): void { this._isPaused = entry.intersectionRatio === 0; if (!this._isPaused && this._needsFullRefresh) { @@ -174,8 +179,8 @@ export class Renderer extends EventEmitter implements IRenderer { /** * Queues a refresh between two rows (inclusive), to be done on next animation * frame. - * @param {number} start The start row. - * @param {number} end The end row. + * @param start The start row. + * @param end The end row. */ public refreshRows(start: number, end: number): void { if (this._isPaused) { diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index e57276d7..d40aa28d 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer'; +import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, NULL_CELL_CODE } from '../Buffer'; import { FLAGS, IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; import { CharData, ITerminal } from '../Types'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; @@ -124,7 +124,7 @@ export class TextRenderLayer extends BaseRenderLayer { // get removed, and `a` would not re-render because it thinks it's // already in the correct state. // this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA; - if (lastCharX < line.length - 1 && line[lastCharX + 1][CHAR_DATA_CODE_INDEX] === 32 /*' '*/) { + if (lastCharX < line.length - 1 && line[lastCharX + 1][CHAR_DATA_CODE_INDEX] === NULL_CELL_CODE) { width = 2; // this._clearChar(x + 1, y); // The overlapping char's char data will force a clear and render when the diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index 4006f28c..824304ed 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -66,7 +66,7 @@ export interface IRenderDimensions { actualCellHeight: number; } -export interface IRenderLayer { +export interface IRenderLayer extends IDisposable { /** * Called when the terminal loses focus. */ diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index e336c79e..32dea6ed 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -81,6 +81,15 @@ export class DomRenderer extends EventEmitter implements IRenderer { this._terminal.screenElement.appendChild(this._selectionContainer); } + public dispose(): void { + this._terminal.element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass); + this._terminal.screenElement.removeChild(this._rowContainer); + this._terminal.screenElement.removeChild(this._selectionContainer); + this._terminal.screenElement.removeChild(this._themeStyleElement); + this._terminal.screenElement.removeChild(this._dimensionsStyleElement); + super.dispose(); + } + private _updateDimensions(): void { this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio; this.dimensions.scaledCharHeight = this._terminal.charMeasure.height * window.devicePixelRatio; diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index e91c71fe..c90dd6e5 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -7,7 +7,7 @@ import jsdom = require('jsdom'); import { assert } from 'chai'; import { DomRendererRowFactory } from './DomRendererRowFactory'; import { LineData } from '../../Types'; -import { DEFAULT_ATTR } from '../../Buffer'; +import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '../../Buffer'; import { FLAGS } from '../Types'; describe('DomRendererRowFactory', () => { @@ -149,7 +149,7 @@ describe('DomRendererRowFactory', () => { function createEmptyLineData(cols: number): LineData { const lineData: LineData = []; for (let i = 0; i < cols; i++) { - lineData.push([DEFAULT_ATTR, ' ', 1, 32 /* ' '.charCodeAt(0) */]); + lineData.push([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); } return lineData; } diff --git a/src/shared/utils/Browser.ts b/src/shared/utils/Browser.ts index a023031c..42c20d9d 100644 --- a/src/shared/utils/Browser.ts +++ b/src/shared/utils/Browser.ts @@ -22,8 +22,8 @@ export const isLinux = platform.indexOf('Linux') >= 0; /** * Return if the given array contains the given element - * @param {Array} array The array to search for the given element. - * @param {Object} el The element to look for into the array + * @param arr The array to search for the given element. + * @param el The element to look for into the array */ function contains(arr: any[], el: any): boolean { return arr.indexOf(el) >= 0; diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 78a1942b..e2e8e393 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -5,7 +5,7 @@ import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Types'; import { LineData, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ICircularList, ILinkifier, IMouseHelper, ILinkMatcherOptions, XtermListener, CharacterJoinerHandler } from '../Types'; -import { Buffer } from '../Buffer'; +import { Buffer, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '../Buffer'; import * as Browser from '../shared/utils/Browser'; import { ITheme, IDisposable, IMarker } from 'xterm'; @@ -152,7 +152,7 @@ export class MockTerminal implements ITerminal { const line: LineData = []; cols = cols || this.cols; for (let i = 0; i < cols; i++) { - line.push([0, ' ', 1, 32]); + line.push([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); } return line; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index b4df9210..1db05639 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -416,6 +416,13 @@ declare module 'xterm' { */ off(type: 'blur' | 'focus' | 'linefeed' | 'selection' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void; + /** + * Emits an event on the terminal. + * @param type The type of event + * @param data data associated with the event. + * @deprecated This is being removed from the API with no replacement, see + * issue #1505. + */ emit(type: string, data?: any): void; addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable; @@ -474,7 +481,7 @@ declare module 'xterm' { * (EXPERIMENTAL) Registers a character joiner, allowing custom sequences of * characters to be rendered as a single unit. This is useful in particular * for rendering ligatures and graphemes, among other things. - * + * * Each registered character joiner is called with a string of text * representing a portion of a line in the terminal that can be rendered as * a single unit. The joiner must return a sorted array, where each entry is @@ -483,16 +490,16 @@ declare module 'xterm' { * a single unit. When multiple joiners are provided, the results of each * are collected. If there are any overlapping substrings between them, they * are combined into one larger unit that is drawn together. - * + * * All character joiners that are registered get called every time a line is * rendered in the terminal, so it is essential for the handler function to * run as quickly as possible to avoid slowdowns when rendering. Similarly, * joiners should strive to return the smallest possible substrings to * render together, since they aren't drawn as optimally as individual * characters. - * + * * NOTE: character joiners are only used by the canvas renderer. - * + * * @param handler The function that determines character joins. It is called * with a string of text that is eligible for joining and returns an array * where each entry is an array containing the start (inclusive) and end