diff --git a/README.md b/README.md
index 93e7f262..730fc9af 100644
--- a/README.md
+++ b/README.md
@@ -122,6 +122,9 @@ computational environment for Jupyter, supporting interactive data science and s
- [**Kubebox**](https://github.com/astefanutti/kubebox): Terminal console for Kubernetes clusters.
- [**Azure Cloud Shell**](https://shell.azure.com): Azure Cloud Shell is a Microsoft-managed admin machine built on Azure, for Azure.
- [**atom-xterm**](https://atom.io/packages/atom-xterm): Atom plugin for providing terminals inside your Atom workspace.
+- [**rtty**](https://github.com/zhaojh329/rtty): A reverse proxy WebTTY. It is composed of the client and the server.
+- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS
+- [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker.
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.
@@ -163,6 +166,10 @@ Xterm.js is maintained by [SourceLair](https://www.sourcelair.com/) and a few ex
To contribute either code, documentation or issues to xterm.js please read the [Contributing document](CONTRIBUTING.md) beforehand. The development of xterm.js does not require any special tool. All you need is an editor that supports JavaScript/TypeScript and a browser. You will need Node.js installed locally to get all the features working in the demo.
+### Code structure
+
+`src/` is roughly split up into areas of functionality such as `renderer/` that handles all rendering and `utils/` which provides general utility functions. The `shared/` folder contains code that can be used from either the main thread or a web worker thread, all code inside a `shared/` folder should only ever import other code from a `shared/` folder to minimize the amount of code run what launching a web worker.
+
## License Agreement
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.
diff --git a/demo/index.html b/demo/index.html
index 93399f9c..da348f5e 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -23,6 +23,9 @@
cursorBlink
+
+ macOptionIsMeta
+
cursorStyle
diff --git a/demo/main.js b/demo/main.js
index 95b15a0d..1faf77d8 100644
--- a/demo/main.js
+++ b/demo/main.js
@@ -27,6 +27,7 @@ var terminalContainer = document.getElementById('terminal-container'),
optionElements = {
cursorBlink: document.querySelector('#option-cursor-blink'),
cursorStyle: document.querySelector('#option-cursor-style'),
+ macOptionIsMeta: document.querySelector('#option-mac-option-is-meta'),
scrollback: document.querySelector('#option-scrollback'),
tabstopwidth: document.querySelector('#option-tabstopwidth'),
bellStyle: document.querySelector('#option-bell-style')
@@ -72,6 +73,9 @@ optionElements.cursorStyle.addEventListener('change', function () {
optionElements.bellStyle.addEventListener('change', function () {
term.setOption('bellStyle', optionElements.bellStyle.value);
});
+optionElements.macOptionIsMeta.addEventListener('change', function () {
+ term.setOption('macOptionIsMeta', optionElements.macOptionIsMeta.checked);
+});
optionElements.scrollback.addEventListener('change', function () {
term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10));
});
@@ -87,6 +91,7 @@ function createTerminal() {
terminalContainer.removeChild(terminalContainer.children[0]);
}
term = new Terminal({
+ macOptionIsMeta: optionElements.macOptionIsMeta.enabled,
cursorBlink: optionElements.cursorBlink.checked,
scrollback: parseInt(optionElements.scrollback.value, 10),
tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10)
diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts
index 5b646299..a8affbf3 100644
--- a/fixtures/typings-test/typings-test.ts
+++ b/fixtures/typings-test/typings-test.ts
@@ -144,7 +144,10 @@ namespace methods_core {
const r20: string = t.getOption('bellStyle');
const r21: boolean = t.getOption('enableBold');
const r22: number = t.getOption('letterSpacing');
- const r23: boolean = t.getOption('rightClickSelectsWord');
+ const r23: boolean = t.getOption('macOptionIsMeta');
+ const r24: string = t.getOption('fontWeight');
+ const r25: string = t.getOption('fontWeightBold');
+ const r26: boolean = t.getOption('rightClickSelectsWord');
}
{
const t: Terminal = new Terminal();
@@ -158,6 +161,10 @@ namespace methods_core {
t.setOption('debug', true);
t.setOption('disableStdin', true);
t.setOption('enableBold', true);
+ t.setOption('fontWeight', 'normal');
+ t.setOption('fontWeight', 'bold');
+ t.setOption('fontWeightBold', 'normal');
+ t.setOption('fontWeightBold', 'bold');
t.setOption('popOnBell', true);
t.setOption('screenKeys', true);
t.setOption('useFlowControl', true);
@@ -178,6 +185,7 @@ namespace methods_core {
t.setOption('lineHeight', 1);
t.setOption('fontFamily', 'foo');
t.setOption('theme', {background: '#ff0000'});
+ t.setOption('macOptionIsMeta', true);
t.setOption('rightClickSelectsWord', false);
}
}
diff --git a/package.json b/package.json
index 6b1eb230..cf3df57b 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
- "version": "3.0.0",
+ "version": "3.1.0-master",
"ignore": [
"demo",
"test",
diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts
index 210d2971..0607a573 100644
--- a/src/Buffer.test.ts
+++ b/src/Buffer.test.ts
@@ -4,7 +4,7 @@
*/
import { assert } from 'chai';
-import { ITerminal } from './Interfaces';
+import { ITerminal } from './Types';
import { Buffer } from './Buffer';
import { CircularList } from './utils/CircularList';
import { MockTerminal } from './utils/TestUtils.test';
diff --git a/src/Buffer.ts b/src/Buffer.ts
index 623c3854..462cde56 100644
--- a/src/Buffer.ts
+++ b/src/Buffer.ts
@@ -3,9 +3,8 @@
* @license MIT
*/
-import { ITerminal, IBuffer } from './Interfaces';
import { CircularList } from './utils/CircularList';
-import { LineData, CharData } from './Types';
+import { LineData, CharData, ITerminal, IBuffer } from './Types';
export const CHAR_DATA_ATTR_INDEX = 0;
export const CHAR_DATA_CHAR_INDEX = 1;
@@ -121,11 +120,6 @@ export class Buffer implements IBuffer {
if (this._terminal.cols < newCols) {
const ch: CharData = [this._terminal.defAttr, ' ', 1, 32]; // does xterm use the default attr?
for (let i = 0; i < this._lines.length; i++) {
- // TODO: This should be removed, with tests setup for the case that was
- // causing the underlying bug, see https://github.com/sourcelair/xterm.js/issues/824
- if (this._lines.get(i) === undefined) {
- this._lines.set(i, this._terminal.blankLine(undefined, undefined, newCols));
- }
while (this._lines.get(i).length < newCols) {
this._lines.get(i).push(ch);
}
@@ -182,16 +176,13 @@ export class Buffer implements IBuffer {
}
// Make sure that the cursor stays on screen
- if (this.y >= newRows) {
- this.y = newRows - 1;
- }
+ this.x = Math.min(this.x, newCols - 1);
+ this.y = Math.min(this.y, newRows - 1);
if (addToY) {
this.y += addToY;
}
-
- if (this.x >= newCols) {
- this.x = newCols - 1;
- }
+ this.savedY = Math.min(this.savedY, newRows - 1);
+ this.savedX = Math.min(this.savedX, newCols - 1);
this.scrollTop = 0;
}
diff --git a/src/BufferSet.test.ts b/src/BufferSet.test.ts
index b9c1824d..009ebf2e 100644
--- a/src/BufferSet.test.ts
+++ b/src/BufferSet.test.ts
@@ -4,7 +4,7 @@
*/
import { assert } from 'chai';
-import { ITerminal } from './Interfaces';
+import { ITerminal } from './Types';
import { BufferSet } from './BufferSet';
import { Buffer } from './Buffer';
import { MockTerminal } from './utils/TestUtils.test';
diff --git a/src/BufferSet.ts b/src/BufferSet.ts
index e31d2278..553b2056 100644
--- a/src/BufferSet.ts
+++ b/src/BufferSet.ts
@@ -3,7 +3,7 @@
* @license MIT
*/
-import { ITerminal, IBufferSet } from './Interfaces';
+import { ITerminal, IBufferSet } from './Types';
import { Buffer } from './Buffer';
import { EventEmitter } from './EventEmitter';
@@ -61,24 +61,35 @@ export class BufferSet extends EventEmitter implements IBufferSet {
* Sets the normal Buffer of the BufferSet as its currently active Buffer
*/
public activateNormalBuffer(): void {
+ if (this._activeBuffer === this._normal) {
+ return;
+ }
// The alt buffer should always be cleared when we switch to the normal
// buffer. This frees up memory since the alt buffer should always be new
// when activated.
this._alt.clear();
-
this._activeBuffer = this._normal;
- this.emit('activate', this._normal);
+ this.emit('activate', {
+ activeBuffer: this._normal,
+ inactiveBuffer: this._alt
+ });
}
/**
* Sets the alt Buffer of the BufferSet as its currently active Buffer
*/
public activateAltBuffer(): void {
+ if (this._activeBuffer === this._alt) {
+ return;
+ }
// Since the alt buffer is always cleared when the normal buffer is
// activated, we want to fill it when switching to it.
this._alt.fillViewportRows();
this._activeBuffer = this._alt;
- this.emit('activate', this._alt);
+ this.emit('activate', {
+ activeBuffer: this._alt,
+ inactiveBuffer: this._normal
+ });
}
/**
diff --git a/src/Charsets.ts b/src/Charsets.ts
index e62e3f9c..fe0112ec 100644
--- a/src/Charsets.ts
+++ b/src/Charsets.ts
@@ -3,7 +3,7 @@
* @license MIT
*/
-import { ICharset } from './Interfaces';
+import { ICharset } from './Types';
/**
* The character sets supported by the terminal. These enable several languages
diff --git a/src/CompositionHelper.ts b/src/CompositionHelper.ts
index e588be78..2aa0449f 100644
--- a/src/CompositionHelper.ts
+++ b/src/CompositionHelper.ts
@@ -3,7 +3,7 @@
* @license MIT
*/
-import { ITerminal } from './Interfaces';
+import { ITerminal } from './Types';
interface IPosition {
start: number;
diff --git a/src/EventEmitter.test.ts b/src/EventEmitter.test.ts
index c1f0a0ab..f2d31cf6 100644
--- a/src/EventEmitter.test.ts
+++ b/src/EventEmitter.test.ts
@@ -13,18 +13,6 @@ describe('EventEmitter', () => {
eventEmitter = new EventEmitter();
});
- describe('once', () => {
- it('should trigger the listener only once', () => {
- let count = 0;
- const listener = () => count++;
- eventEmitter.once('test', listener);
- eventEmitter.emit('test');
- assert.equal(count, 1);
- eventEmitter.emit('test');
- assert.equal(count, 1);
- });
- });
-
describe('emit', () => {
it('should emit events to listeners', () => {
let count1 = 0;
diff --git a/src/EventEmitter.ts b/src/EventEmitter.ts
index 414eac89..440eab2b 100644
--- a/src/EventEmitter.ts
+++ b/src/EventEmitter.ts
@@ -3,10 +3,10 @@
* @license MIT
*/
-import { IEventEmitter, IListenerType } from './Interfaces';
+import { IEventEmitter } from 'xterm';
export class EventEmitter implements IEventEmitter {
- private _events: {[type: string]: IListenerType[]};
+ private _events: {[type: string]: ((...args: any[]) => void)[]};
constructor() {
// Restore the previous events if available, this will happen if the
@@ -14,12 +14,12 @@ export class EventEmitter implements IEventEmitter {
this._events = this._events || {};
}
- public on(type: string, listener: IListenerType): void {
+ public on(type: string, listener: ((...args: any[]) => void)): void {
this._events[type] = this._events[type] || [];
this._events[type].push(listener);
}
- public off(type: string, listener: IListenerType): void {
+ public off(type: string, listener: ((...args: any[]) => void)): void {
if (!this._events[type]) {
return;
}
@@ -28,7 +28,7 @@ export class EventEmitter implements IEventEmitter {
let i = obj.length;
while (i--) {
- if (obj[i] === listener || obj[i].listener === listener) {
+ if (obj[i] === listener) {
obj.splice(i, 1);
return;
}
@@ -41,16 +41,6 @@ export class EventEmitter implements IEventEmitter {
}
}
- public once(type: string, listener: IListenerType): void {
- function on(): void {
- let args = Array.prototype.slice.call(arguments);
- this.off(type, on);
- listener.apply(this, args);
- }
- (on).listener = listener;
- this.on(type, on);
- }
-
public emit(type: string, ...args: any[]): void {
if (!this._events[type]) {
return;
@@ -61,7 +51,7 @@ export class EventEmitter implements IEventEmitter {
}
}
- public listeners(type: string): IListenerType[] {
+ public listeners(type: string): ((...args: any[]) => void)[] {
return this._events[type] || [];
}
diff --git a/src/InputHandler.ts b/src/InputHandler.ts
index 7d3a4d24..f71117f8 100644
--- a/src/InputHandler.ts
+++ b/src/InputHandler.ts
@@ -4,10 +4,9 @@
* @license MIT
*/
-import { IInputHandler, ITerminal, IInputHandlingTerminal } from './Interfaces';
+import { CharData, IInputHandler, IInputHandlingTerminal, ITerminal } from './Types';
import { C0 } from './EscapeSequences';
import { DEFAULT_CHARSET } from './Charsets';
-import { CharData } from './Types';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer';
import { FLAGS } from './renderer/Types';
import { wcwidth } from './CharWidth';
@@ -928,7 +927,6 @@ export class InputHandler implements IInputHandler {
case 47: // alt screen buffer
case 1047: // alt screen buffer
this._terminal.buffers.activateAltBuffer();
- this._terminal.selectionManager.setBuffer(this._terminal.buffer);
this._terminal.viewport.syncScrollArea();
this._terminal.showCursor();
break;
@@ -1100,7 +1098,6 @@ export class InputHandler implements IInputHandler {
// if (params[0] === 1049) {
// this.restoreCursor(params);
// }
- this._terminal.selectionManager.setBuffer(this._terminal.buffer);
this._terminal.refresh(0, this._terminal.rows - 1);
this._terminal.viewport.syncScrollArea();
this._terminal.showCursor();
diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts
index 8b66dcaa..14b1ac0f 100644
--- a/src/Linkifier.test.ts
+++ b/src/Linkifier.test.ts
@@ -4,10 +4,9 @@
*/
import { assert } from 'chai';
-import { ITerminal, ILinkifier, ILinkMatcher, IBuffer, IBufferAccessor, IElementAccessor } from './Interfaces';
+import { IMouseZoneManager, IMouseZone } from './input/Types';
+import { ILinkMatcher, LineData, ITerminal, ILinkifier, IBuffer, IBufferAccessor, IElementAccessor } from './Types';
import { Linkifier } from './Linkifier';
-import { LineData } from './Types';
-import { IMouseZoneManager, IMouseZone } from './input/Interfaces';
import { MockBuffer } from './utils/TestUtils.test';
import { CircularList } from './utils/CircularList';
diff --git a/src/Linkifier.ts b/src/Linkifier.ts
index 76771930..da901a6e 100644
--- a/src/Linkifier.ts
+++ b/src/Linkifier.ts
@@ -3,9 +3,8 @@
* @license MIT
*/
-import { ILinkHoverEvent, ILinkMatcher, ILinkMatcherOptions, ITerminal, IBufferAccessor, ILinkifier, IElementAccessor } from './Interfaces';
-import { LinkMatcherHandler, LinkMatcherValidationCallback, LineData, LinkHoverEventTypes } from './Types';
-import { IMouseZoneManager } from './input/Interfaces';
+import { IMouseZoneManager } from './input/Types';
+import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback, LineData, LinkHoverEventTypes, ILinkMatcherOptions, ITerminal, IBufferAccessor, ILinkifier, IElementAccessor } from './Types';
import { MouseZone } from './input/MouseZoneManager';
import { EventEmitter } from './EventEmitter';
@@ -82,12 +81,12 @@ export class Linkifier extends EventEmitter implements ILinkifier {
}
// Increase range to linkify
- if (!this._rowsToLinkify.start) {
+ if (this._rowsToLinkify.start === null) {
this._rowsToLinkify.start = start;
this._rowsToLinkify.end = end;
} else {
- this._rowsToLinkify.start = this._rowsToLinkify.start < start ? this._rowsToLinkify.start : start;
- this._rowsToLinkify.end = this._rowsToLinkify.end > end ? this._rowsToLinkify.end : end;
+ this._rowsToLinkify.start = Math.min(this._rowsToLinkify.start, start);
+ this._rowsToLinkify.end = Math.max(this._rowsToLinkify.end, end);
}
// Clear out any existing links on this row range
diff --git a/src/Parser.ts b/src/Parser.ts
index 6f4b0932..3ac03e4f 100644
--- a/src/Parser.ts
+++ b/src/Parser.ts
@@ -5,7 +5,7 @@
*/
import { C0 } from './EscapeSequences';
-import { IInputHandler } from './Interfaces';
+import { IInputHandler } from './Types';
import { CHARSETS, DEFAULT_CHARSET } from './Charsets';
const normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {};
diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts
index 9a9cf929..a76947e5 100644
--- a/src/SelectionManager.test.ts
+++ b/src/SelectionManager.test.ts
@@ -5,14 +5,13 @@
import jsdom = require('jsdom');
import { assert } from 'chai';
-import { ITerminal, ICircularList, IBuffer } from './Interfaces';
import { CharMeasure } from './utils/CharMeasure';
import { CircularList } from './utils/CircularList';
import { SelectionManager } from './SelectionManager';
import { SelectionModel } from './SelectionModel';
import { BufferSet } from './BufferSet';
+import { LineData, CharData, ITerminal, ICircularList, IBuffer } from './Types';
import { MockTerminal } from './utils/TestUtils.test';
-import { LineData, CharData } from './Types';
class TestMockTerminal extends MockTerminal {
emit(event: string, data: any): void {}
@@ -21,10 +20,9 @@ class TestMockTerminal extends MockTerminal {
class TestSelectionManager extends SelectionManager {
constructor(
terminal: ITerminal,
- buffer: IBuffer,
charMeasure: CharMeasure
) {
- super(terminal, buffer, charMeasure);
+ super(terminal, charMeasure);
}
public get model(): SelectionModel { return this._model; }
@@ -59,7 +57,7 @@ describe('SelectionManager', () => {
terminal.buffers = new BufferSet(terminal);
terminal.buffer = terminal.buffers.active;
buffer = terminal.buffer;
- selectionManager = new TestSelectionManager(terminal, buffer, null);
+ selectionManager = new TestSelectionManager(terminal, null);
});
function stringToRow(text: string): LineData {
diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts
index e68bb6a0..a4a0b80c 100644
--- a/src/SelectionManager.ts
+++ b/src/SelectionManager.ts
@@ -3,14 +3,13 @@
* @license MIT
*/
+import { ITerminal, ICircularList, ISelectionManager, IBuffer, LineData, CharData } from './Types';
import { MouseHelper } from './utils/MouseHelper';
-import * as Browser from './utils/Browser';
+import * as Browser from './shared/utils/Browser';
import { CharMeasure } from './utils/CharMeasure';
import { CircularList } from './utils/CircularList';
import { EventEmitter } from './EventEmitter';
-import { ITerminal, ICircularList, ISelectionManager, IBuffer } from './Interfaces';
import { SelectionModel } from './SelectionModel';
-import { LineData, CharData } from './Types';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';
/**
@@ -95,10 +94,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
private _mouseMoveListener: EventListener;
private _mouseUpListener: EventListener;
+ private _trimListener: (...args: any[]) => void;
constructor(
private _terminal: ITerminal,
- private _buffer: IBuffer,
private _charMeasure: CharMeasure
) {
super();
@@ -109,18 +108,24 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
this._activeSelectionMode = SelectionMode.NORMAL;
}
+ private get _buffer(): IBuffer {
+ return this._terminal.buffers.active;
+ }
+
/**
* Initializes listener variables.
*/
private _initListeners(): void {
this._mouseMoveListener = event => this._onMouseMove(event);
this._mouseUpListener = event => this._onMouseUp(event);
+ this._trimListener = (amount: number) => this._onTrim(amount);
- // Only adjust the selection on trim, shiftElements is rarely used (only in
- // reverseIndex) and delete in a splice is only ever used when the same
- // number of elements was just added. Given this is could actually be
- // beneficial to leave the selection as is for these cases.
- this._buffer.lines.on('trim', (amount: number) => this._onTrim(amount));
+ this.initBuffersListeners();
+ }
+
+ public initBuffersListeners(): void {
+ this._terminal.buffer.lines.on('trim', this._trimListener);
+ this._terminal.buffers.on('activate', e => this._onBufferActivate(e));
}
/**
@@ -139,16 +144,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
this._enabled = true;
}
- /**
- * Sets the active buffer, this should be called when the alt buffer is
- * switched in or out.
- * @param buffer The active buffer.
- */
- public setBuffer(buffer: IBuffer): void {
- this._buffer = buffer;
- this.clearSelection();
- }
-
public get selectionStart(): [number, number] { return this._model.finalSelectionStart; }
public get selectionEnd(): [number, number] { return this._model.finalSelectionEnd; }
@@ -580,6 +575,16 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
this._terminal.emit('selection');
}
+ private _onBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void {
+ this.clearSelection();
+ // Only adjust the selection on trim, shiftElements is rarely used (only in
+ // reverseIndex) and delete in a splice is only ever used when the same
+ // number of elements was just added. Given this is could actually be
+ // beneficial to leave the selection as is for these cases.
+ e.inactiveBuffer.lines.off('trim', this._trimListener);
+ e.activeBuffer.lines.on('trim', this._trimListener);
+ }
+
/**
* Converts a viewport column to the character index on the buffer line, the
* latter takes into account wide characters.
diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts
index eda94718..ed483dbe 100644
--- a/src/SelectionModel.test.ts
+++ b/src/SelectionModel.test.ts
@@ -4,7 +4,7 @@
*/
import { assert } from 'chai';
-import { ITerminal } from './Interfaces';
+import { ITerminal } from './Types';
import { SelectionModel } from './SelectionModel';
import { BufferSet } from './BufferSet';
import { MockTerminal } from './utils/TestUtils.test';
diff --git a/src/SelectionModel.ts b/src/SelectionModel.ts
index 5982b1e9..a9a3c89e 100644
--- a/src/SelectionModel.ts
+++ b/src/SelectionModel.ts
@@ -3,7 +3,7 @@
* @license MIT
*/
-import { ITerminal } from './Interfaces';
+import { ITerminal } from './Types';
/**
* Represents a selection within the buffer. This model only cares about column
diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts
index 9cb0b75c..946e9efb 100644
--- a/src/Terminal.test.ts
+++ b/src/Terminal.test.ts
@@ -503,6 +503,9 @@ describe('term.js addons', () => {
it('should return \\x1b[5C for alt+right', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C
});
+ it('should return \\x1ba for alt+a', () => {
+ assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, '\x1ba');
+ });
});
describe('On macOS platforms', () => {
@@ -515,6 +518,19 @@ describe('term.js addons', () => {
it('should return \\x1bf for alt+right', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1bf'); // CSI 5 C
});
+ it('should return undefined for alt+a', () => {
+ assert.strictEqual(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, undefined);
+ });
+ });
+
+ describe('with macOptionIsMeta', () => {
+ beforeEach(() => {
+ term.browser.isMac = true;
+ term.setOption('macOptionIsMeta', true);
+ });
+ it('should return \\x1ba for alt+a', () => {
+ assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 65 }).key, '\x1ba');
+ });
});
it('should return \\x1b[5A for alt+up', () => {
@@ -597,6 +613,22 @@ describe('term.js addons', () => {
};
});
+ describe('with macOptionIsMeta', () => {
+ beforeEach(() => {
+ term.browser.isMac = true;
+ term.setOption('macOptionIsMeta', true);
+ });
+
+ it('should interfere with the alt key on keyDown', () => {
+ evKeyDown.altKey = true;
+ evKeyDown.keyCode = 81;
+ assert.equal(term.keyDown(evKeyDown), false);
+ evKeyDown.altKey = true;
+ evKeyDown.keyCode = 192;
+ assert.equal(term.keyDown(evKeyDown), false);
+ });
+ });
+
describe('On Mac OS', () => {
beforeEach(() => {
term.browser.isMac = true;
diff --git a/src/Terminal.ts b/src/Terminal.ts
index 8af90e83..2b9ba73e 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -21,6 +21,9 @@
* http://linux.die.net/man/7/urxvt
*/
+import { ICharset, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types';
+import { IMouseZoneManager } from './input/Types';
+import { IRenderer } from './renderer/Types';
import { BufferSet } from './BufferSet';
import { Buffer, MAX_BUFFER_SIZE } from './Buffer';
import { CompositionHelper } from './CompositionHelper';
@@ -35,17 +38,13 @@ import { Renderer } from './renderer/Renderer';
import { Linkifier } from './Linkifier';
import { SelectionManager } from './SelectionManager';
import { CharMeasure } from './utils/CharMeasure';
-import * as Browser from './utils/Browser';
+import * as Browser from './shared/utils/Browser';
import { MouseHelper } from './utils/MouseHelper';
import { CHARSETS } from './Charsets';
-import { CustomKeyEventHandler, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types';
-import { ITerminal, IBrowser, ICharset, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper, ITheme, ILinkifier } from './Interfaces';
import { BELL_SOUND } from './utils/Sounds';
import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager';
-import { IMouseZoneManager } from './input/Interfaces';
import { MouseZoneManager } from './input/MouseZoneManager';
-import { initialize as initializeCharAtlas } from './renderer/CharAtlas';
-import { IRenderer } from './renderer/Interfaces';
+import { ITheme } from 'xterm';
// Let it work inside Node.js for automated testing purposes.
const document = (typeof window !== 'undefined') ? window.document : null;
@@ -75,11 +74,14 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
enableBold: true,
fontFamily: 'courier-new, courier, monospace',
fontSize: 15,
+ fontWeight: 'normal',
+ fontWeightBold: 'bold',
lineHeight: 1.0,
letterSpacing: 0,
scrollback: 1000,
screenKeys: false,
debug: false,
+ macOptionIsMeta: false,
cancelEvents: false,
disableStdin: false,
useFlowControl: false,
@@ -193,7 +195,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
public selectionManager: SelectionManager;
public linkifier: ILinkifier;
public buffers: BufferSet;
- public buffer: Buffer;
public viewport: IViewport;
private compositionHelper: ICompositionHelper;
public charMeasure: CharMeasure;
@@ -294,17 +295,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// Create the terminal's buffers and set the current buffer
this.buffers = new BufferSet(this);
- this.buffer = this.buffers.active; // Convenience shortcut;
- this.buffers.on('activate', (buffer: Buffer) => {
- this.buffer = buffer;
- });
-
- // Ensure the selection manager has the correct buffer
if (this.selectionManager) {
- this.selectionManager.setBuffer(this.buffer);
+ this.selectionManager.clearSelection();
+ this.selectionManager.initBuffersListeners();
}
}
+ /**
+ * Convenience property to active buffer.
+ */
+ public get buffer(): Buffer {
+ return this.buffers.active;
+ }
+
/**
* back_color_erase feature for xterm.
*/
@@ -362,6 +365,16 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
value = 'block';
}
break;
+ case 'fontWeight':
+ if (!value) {
+ value = 'normal';
+ }
+ break;
+ case 'fontWeightBold':
+ if (!value) {
+ value = 'bold';
+ }
+ break;
case 'lineHeight':
if (value < 1) {
console.warn(`${key} cannot be less than 1, value: ${value}`);
@@ -415,11 +428,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
case 'enableBold':
case 'letterSpacing':
case 'lineHeight':
+ case 'fontWeight':
+ case 'fontWeightBold':
+ const didCharSizeChange = (key === 'fontWeight' || key === 'fontWeightBold' || key === 'enableBold');
+
// When the font changes the size of the cells may change which requires a renderer clear
this.renderer.clear();
- this.renderer.onResize(this.cols, this.rows, false);
+ this.renderer.onResize(this.cols, this.rows, didCharSizeChange);
this.refresh(0, this.rows - 1);
- // this.charMeasure.measure(this.options);
case 'scrollback':
this.buffers.resize(this.cols, this.rows);
this.viewport.syncScrollArea();
@@ -575,8 +591,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.document = this.parent.ownerDocument;
this.body = this.document.body;
- initializeCharAtlas(this.document);
-
// Create main element container
this.element = this.document.createElement('div');
this.element.classList.add('terminal');
@@ -641,7 +655,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows, true));
this.renderer.on('resize', (dimensions) => this.viewport.syncScrollArea());
- this.selectionManager = new SelectionManager(this, this.buffer, this.charMeasure);
+ this.selectionManager = new SelectionManager(this, this.charMeasure);
this.element.addEventListener('mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e));
this.selectionManager.on('refresh', data => this.renderer.onSelectionChanged(data.start, data.end));
this.selectionManager.on('newselection', text => {
@@ -1374,7 +1388,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
return this.cancel(ev, true);
}
- if (isThirdLevelShift(this.browser, ev)) {
+ if (this._isThirdLevelShift(this.browser, ev)) {
return true;
}
@@ -1395,6 +1409,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
return this.cancel(ev, true);
}
+ private _isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean {
+ const thirdLevelKey =
+ (browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
+ (browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
+
+ if (ev.type === 'keypress') {
+ return thirdLevelKey;
+ }
+
+ // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)
+ return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);
+ }
+
/**
* Returns an object that determines how a KeyboardEvent should be handled. The key of the
* returned value is the new key code to pass to the PTY.
@@ -1699,8 +1726,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// ^] - Operating System Command (OSC)
result.key = String.fromCharCode(29);
}
- } else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) {
- // On Mac this is a third level shift. Use instead.
+ } else if ((!this.browser.isMac || this.options.macOptionIsMeta) && ev.altKey && !ev.ctrlKey && !ev.metaKey) {
+ // On macOS this is a third level shift when !macOptionIsMeta. Use instead.
if (ev.keyCode >= 65 && ev.keyCode <= 90) {
result.key = C0.ESC + String.fromCharCode(ev.keyCode + 32);
} else if (ev.keyCode === 192) {
@@ -1766,7 +1793,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
}
if (!key || (
- (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this.browser, ev)
+ (ev.altKey || ev.ctrlKey || ev.metaKey) && !this._isThirdLevelShift(this.browser, ev)
)) {
return false;
}
@@ -2080,11 +2107,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.options.cols = this.cols;
const customKeyEventHandler = this.customKeyEventHandler;
const inputHandler = this.inputHandler;
- const buffers = this.buffers;
this.setup();
this.customKeyEventHandler = customKeyEventHandler;
this.inputHandler = inputHandler;
- this.buffers = buffers;
this.refresh(0, this.rows - 1);
this.viewport.syncScrollArea();
}
@@ -2113,13 +2138,15 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
}
private visualBell(): boolean {
- return this.options.bellStyle === 'visual' ||
- this.options.bellStyle === 'both';
+ return false;
+ // return this.options.bellStyle === 'visual' ||
+ // this.options.bellStyle === 'both';
}
private soundBell(): boolean {
- return this.options.bellStyle === 'sound' ||
- this.options.bellStyle === 'both';
+ return this.options.bellStyle === 'sound';
+ // return this.options.bellStyle === 'sound' ||
+ // this.options.bellStyle === 'both';
}
private syncBellSound(): void {
@@ -2160,19 +2187,6 @@ function off(el: any, type: string, handler: (event: Event) => any, capture: boo
el.removeEventListener(type, handler, capture);
}
-function isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean {
- const thirdLevelKey =
- (browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
- (browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
-
- if (ev.type === 'keypress') {
- return thirdLevelKey;
- }
-
- // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)
- return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);
-}
-
function wasMondifierKeyOnlyEvent(ev: KeyboardEvent): boolean {
return ev.keyCode === 16 || // Shift
ev.keyCode === 17 || // Ctrl
diff --git a/src/Types.ts b/src/Types.ts
index 3263282e..f3e9bb94 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -3,16 +3,332 @@
* @license MIT
*/
-export type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void;
-export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
+import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter } from 'xterm';
+import { IColorSet, IRenderer } from './renderer/Types';
+import { IMouseZoneManager } from './input/Types';
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
export type CharData = [number, string, number, number];
export type LineData = CharData[];
+export type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void;
+export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
+
export enum LinkHoverEventTypes {
HOVER = 'linkhover',
TOOLTIP = 'linktooltip',
LEAVE = 'linkleave'
}
+
+/**
+ * This interface encapsulates everything needed from the Terminal by the
+ * InputHandler. This cleanly separates the large amount of methods needed by
+ * InputHandler cleanly from the ITerminal interface.
+ */
+export interface IInputHandlingTerminal extends IEventEmitter {
+ element: HTMLElement;
+ options: ITerminalOptions;
+ cols: number;
+ rows: number;
+ charset: ICharset;
+ gcharset: number;
+ glevel: number;
+ charsets: ICharset[];
+ applicationKeypad: boolean;
+ applicationCursor: boolean;
+ originMode: boolean;
+ insertMode: boolean;
+ wraparoundMode: boolean;
+ bracketedPasteMode: boolean;
+ defAttr: number;
+ curAttr: number;
+ prefix: string;
+ savedCols: number;
+ x10Mouse: boolean;
+ vt200Mouse: boolean;
+ normalMouse: boolean;
+ mouseEvents: boolean;
+ sendFocus: boolean;
+ utfMouse: boolean;
+ sgrMouse: boolean;
+ urxvtMouse: boolean;
+ cursorHidden: boolean;
+
+ buffers: IBufferSet;
+ buffer: IBuffer;
+ viewport: IViewport;
+ selectionManager: ISelectionManager;
+
+ bell(): void;
+ focus(): void;
+ convertEol: boolean;
+ updateRange(y: number): void;
+ scroll(isWrapped?: boolean): void;
+ setgLevel(g: number): void;
+ eraseAttr(): number;
+ eraseRight(x: number, y: number): void;
+ eraseLine(y: number): void;
+ eraseLeft(x: number, y: number): void;
+ blankLine(cur?: boolean, isWrapped?: boolean): LineData;
+ is(term: string): boolean;
+ send(data: string): void;
+ setgCharset(g: number, charset: ICharset): void;
+ resize(x: number, y: number): void;
+ log(text: string, data?: any): void;
+ reset(): void;
+ showCursor(): void;
+ refresh(start: number, end: number): void;
+ matchColor(r1: number, g1: number, b1: number): number;
+ error(text: string, data?: any): void;
+ setOption(key: string, value: any): void;
+}
+
+export interface IViewport {
+ syncScrollArea(): void;
+ onWheel(ev: WheelEvent): void;
+ onTouchStart(ev: TouchEvent): void;
+ onTouchMove(ev: TouchEvent): void;
+ onThemeChanged(colors: IColorSet): void;
+}
+
+export interface ICompositionHelper {
+ compositionstart(): void;
+ compositionupdate(ev: CompositionEvent): void;
+ compositionend(): void;
+ updateCompositionElements(dontRecurse?: boolean): void;
+ keydown(ev: KeyboardEvent): boolean;
+}
+
+/**
+ * Handles actions generated by the parser.
+ */
+export interface IInputHandler {
+ addChar(char: string, code: number): void;
+
+ /** C0 BEL */ bell(): void;
+ /** C0 LF */ lineFeed(): void;
+ /** C0 CR */ carriageReturn(): void;
+ /** C0 BS */ backspace(): void;
+ /** C0 HT */ tab(): void;
+ /** C0 SO */ shiftOut(): void;
+ /** C0 SI */ shiftIn(): void;
+
+ /** CSI @ */ insertChars(params?: number[]): void;
+ /** CSI A */ cursorUp(params?: number[]): void;
+ /** CSI B */ cursorDown(params?: number[]): void;
+ /** CSI C */ cursorForward(params?: number[]): void;
+ /** CSI D */ cursorBackward(params?: number[]): void;
+ /** CSI E */ cursorNextLine(params?: number[]): void;
+ /** CSI F */ cursorPrecedingLine(params?: number[]): void;
+ /** CSI G */ cursorCharAbsolute(params?: number[]): void;
+ /** CSI H */ cursorPosition(params?: number[]): void;
+ /** CSI I */ cursorForwardTab(params?: number[]): void;
+ /** CSI J */ eraseInDisplay(params?: number[]): void;
+ /** CSI K */ eraseInLine(params?: number[]): void;
+ /** CSI L */ insertLines(params?: number[]): void;
+ /** CSI M */ deleteLines(params?: number[]): void;
+ /** CSI P */ deleteChars(params?: number[]): void;
+ /** CSI S */ scrollUp(params?: number[]): void;
+ /** CSI T */ scrollDown(params?: number[]): void;
+ /** CSI X */ eraseChars(params?: number[]): void;
+ /** CSI Z */ cursorBackwardTab(params?: number[]): void;
+ /** CSI ` */ charPosAbsolute(params?: number[]): void;
+ /** CSI a */ HPositionRelative(params?: number[]): void;
+ /** CSI b */ repeatPrecedingCharacter(params?: number[]): void;
+ /** CSI c */ sendDeviceAttributes(params?: number[]): void;
+ /** CSI d */ linePosAbsolute(params?: number[]): void;
+ /** CSI e */ VPositionRelative(params?: number[]): void;
+ /** CSI f */ HVPosition(params?: number[]): void;
+ /** CSI g */ tabClear(params?: number[]): void;
+ /** CSI h */ setMode(params?: number[]): void;
+ /** CSI l */ resetMode(params?: number[]): void;
+ /** CSI m */ charAttributes(params?: number[]): void;
+ /** CSI n */ deviceStatus(params?: number[]): void;
+ /** CSI p */ softReset(params?: number[]): void;
+ /** CSI q */ setCursorStyle(params?: number[]): void;
+ /** CSI r */ setScrollRegion(params?: number[]): void;
+ /** CSI s */ saveCursor(params?: number[]): void;
+ /** CSI u */ restoreCursor(params?: number[]): void;
+}
+
+export interface ILinkMatcher {
+ id: number;
+ regex: RegExp;
+ handler: LinkMatcherHandler;
+ hoverTooltipCallback?: LinkMatcherHandler;
+ hoverLeaveCallback?: () => void;
+ matchIndex?: number;
+ validationCallback?: LinkMatcherValidationCallback;
+ priority?: number;
+}
+
+export interface ICharset {
+ [key: string]: string;
+}
+
+export interface ILinkHoverEvent {
+ x: number;
+ y: number;
+ length: number;
+}
+
+export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
+ selectionManager: ISelectionManager;
+ charMeasure: ICharMeasure;
+ renderer: IRenderer;
+ browser: IBrowser;
+ writeBuffer: string[];
+ cursorHidden: boolean;
+ cursorState: number;
+ defAttr: number;
+ options: ITerminalOptions;
+ buffer: IBuffer;
+ buffers: IBufferSet;
+ isFocused: boolean;
+ mouseHelper: IMouseHelper;
+ bracketedPasteMode: boolean;
+
+ /**
+ * Emit the 'data' event and populate the given data.
+ * @param data The data to populate in the event.
+ */
+ handler(data: string): void;
+ scrollLines(disp: number, suppressScrollEvent?: boolean): void;
+ cancel(ev: Event, force?: boolean): boolean | void;
+ log(text: string): void;
+ showCursor(): void;
+ blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData;
+}
+
+export interface IBufferAccessor {
+ buffer: IBuffer;
+}
+
+export interface IElementAccessor {
+ element: HTMLElement;
+}
+
+export interface ILinkifierAccessor {
+ linkifier: ILinkifier;
+}
+
+export interface IMouseHelper {
+ getCoords(event: {pageX: number, pageY: number}, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number];
+ getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number };
+}
+
+export interface ICharMeasure {
+ width: number;
+ height: number;
+ measure(options: ITerminalOptions): void;
+}
+
+// TODO: The options that are not in the public API should be reviewed
+export interface ITerminalOptions extends IPublicTerminalOptions {
+ cancelEvents?: boolean;
+ convertEol?: boolean;
+ debug?: boolean;
+ handler?: (data: string) => void;
+ screenKeys?: boolean;
+ termName?: string;
+ useFlowControl?: boolean;
+}
+
+export interface IBuffer {
+ lines: ICircularList;
+ ydisp: number;
+ ybase: number;
+ y: number;
+ x: number;
+ tabs: any;
+ scrollBottom: number;
+ scrollTop: number;
+ savedY: number;
+ savedX: number;
+ isCursorInViewport: boolean;
+ translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string;
+ nextStop(x?: number): number;
+ prevStop(x?: number): number;
+}
+
+export interface IBufferSet extends IEventEmitter {
+ alt: IBuffer;
+ normal: IBuffer;
+ active: IBuffer;
+
+ activateNormalBuffer(): void;
+ activateAltBuffer(): void;
+}
+
+export interface ICircularList extends IEventEmitter {
+ length: number;
+ maxLength: number;
+ forEach: (callbackfn: (value: T, index: number) => void) => void;
+
+ get(index: number): T;
+ set(index: number, value: T): void;
+ push(value: T): void;
+ pop(): T;
+ splice(start: number, deleteCount: number, ...items: T[]): void;
+ trimStart(count: number): void;
+ shiftElements(start: number, count: number, offset: number): void;
+}
+
+export interface ISelectionManager {
+ selectionText: string;
+ selectionStart: [number, number];
+ selectionEnd: [number, number];
+
+ disable(): void;
+ enable(): void;
+ setSelection(row: number, col: number, length: number): void;
+}
+
+export interface ILinkifier extends IEventEmitter {
+ attachToDom(mouseZoneManager: IMouseZoneManager): void;
+ linkifyRows(start: number, end: number): void;
+ setHypertextLinkHandler(handler: LinkMatcherHandler): void;
+ setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void;
+ registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number;
+ deregisterLinkMatcher(matcherId: number): boolean;
+}
+
+export interface ILinkMatcherOptions {
+ /**
+ * The index of the link from the regex.match(text) call. This defaults to 0
+ * (for regular expressions without capture groups).
+ */
+ matchIndex?: number;
+ /**
+ * A callback that validates an individual link, returning true if valid and
+ * false if invalid.
+ */
+ validationCallback?: LinkMatcherValidationCallback;
+ /**
+ * A callback that fires when the mouse hovers over a link.
+ */
+ tooltipCallback?: LinkMatcherHandler;
+ /**
+ * A callback that fires when the mouse leaves a link that was hovered.
+ */
+ leaveCallback?: () => void;
+ /**
+ * The priority of the link matcher, this defines the order in which the link
+ * matcher is evaluated relative to others, from highest to lowest. The
+ * default value is 0.
+ */
+ priority?: number;
+}
+
+export interface IBrowser {
+ isNode: boolean;
+ userAgent: string;
+ platform: string;
+ isFirefox: boolean;
+ isMSIE: boolean;
+ isMac: boolean;
+ isIpad: boolean;
+ isIphone: boolean;
+ isMSWindows: boolean;
+}
diff --git a/src/Viewport.ts b/src/Viewport.ts
index fcac68ad..87ecd69e 100644
--- a/src/Viewport.ts
+++ b/src/Viewport.ts
@@ -3,9 +3,9 @@
* @license MIT
*/
-import { ITerminal, IViewport } from './Interfaces';
+import { IColorSet } from './renderer/Types';
+import { ITerminal, IViewport } from './Types';
import { CharMeasure } from './utils/CharMeasure';
-import { IColorSet } from './renderer/Interfaces';
/**
* Represents the viewport of a terminal, the visible area within the larger buffer of output.
diff --git a/src/addons/attach/Interfaces.ts b/src/addons/attach/Interfaces.ts
new file mode 100644
index 00000000..ab809af9
--- /dev/null
+++ b/src/addons/attach/Interfaces.ts
@@ -0,0 +1,18 @@
+/**
+ * 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 } from 'xterm';
+
+export interface IAttachAddonTerminal extends Terminal {
+ __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
index a13885bb..018cfb31 100644
--- a/src/addons/attach/attach.test.ts
+++ b/src/addons/attach/attach.test.ts
@@ -12,7 +12,7 @@ class MockTerminal {}
describe('attach addon', () => {
describe('apply', () => {
it('should do register the `attach` and `detach` methods', () => {
- attach.apply(MockTerminal);
+ 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
index ba938695..cbfd4a24 100644
--- a/src/addons/attach/attach.ts
+++ b/src/addons/attach/attach.ts
@@ -5,38 +5,42 @@
* Implements the attach method, that attaches the terminal to a WebSocket stream.
*/
+///
+
+import { Terminal } from 'xterm';
+import { IAttachAddonTerminal } from './Interfaces';
+
/**
* Attaches the given terminal to the given socket.
*
- * @param {Terminal} term - The terminal to be attached to the given socket.
- * @param {WebSocket} socket - The socket to attach the current terminal.
- * @param {boolean} bidirectional - Whether the terminal should send data
- * to the socket as well.
- * @param {boolean} buffered - Whether the rendering of incoming data
- * should happen instantly or at a maximum
- * frequency of 1 rendering per 10ms.
+ * @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: any, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
+export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
+ const addonTerminal = term;
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
- term.socket = socket;
+ addonTerminal.__socket = socket;
- term._flushBuffer = () => {
- term.write(term._attachSocketBuffer);
- term._attachSocketBuffer = null;
+ addonTerminal.__flushBuffer = () => {
+ addonTerminal.write(addonTerminal.__attachSocketBuffer);
+ addonTerminal.__attachSocketBuffer = null;
};
- term._pushToBuffer = (data: string) => {
- if (term._attachSocketBuffer) {
- term._attachSocketBuffer += data;
+ addonTerminal.__pushToBuffer = (data: string) => {
+ if (addonTerminal.__attachSocketBuffer) {
+ addonTerminal.__attachSocketBuffer += data;
} else {
- term._attachSocketBuffer = data;
- setTimeout(term._flushBuffer, 10);
+ addonTerminal.__attachSocketBuffer = data;
+ setTimeout(addonTerminal.__flushBuffer, 10);
}
};
let myTextDecoder;
- term._getMessage = function(ev: MessageEvent): void {
+ addonTerminal.__getMessage = function(ev: MessageEvent): void {
let str;
if (typeof ev.data === 'object') {
if (ev.data instanceof ArrayBuffer) {
@@ -51,71 +55,68 @@ export function attach(term: any, socket: WebSocket, bidirectional: boolean, buf
}
if (buffered) {
- term._pushToBuffer(str || ev.data);
+ addonTerminal.__pushToBuffer(str || ev.data);
} else {
- term.write(str || ev.data);
+ addonTerminal.write(str || ev.data);
}
};
- term._sendData = (data: string) => {
+ addonTerminal.__sendData = (data: string) => {
if (socket.readyState !== 1) {
return;
}
socket.send(data);
};
- socket.addEventListener('message', term._getMessage);
+ socket.addEventListener('message', addonTerminal.__getMessage);
if (bidirectional) {
- term.on('data', term._sendData);
+ addonTerminal.on('data', addonTerminal.__sendData);
}
- socket.addEventListener('close', term.detach.bind(term, socket));
- socket.addEventListener('error', term.detach.bind(term, socket));
+ socket.addEventListener('close', () => detach(addonTerminal, socket));
+ socket.addEventListener('error', () => detach(addonTerminal, socket));
}
/**
* Detaches the given terminal from the given socket
*
- * @param {Terminal} term - The terminal to be detached from the given socket.
- * @param {WebSocket} socket - The socket from which to detach the current
- * terminal.
+ * @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: any, socket: WebSocket): void {
- term.off('data', term._sendData);
+export function detach(term: Terminal, socket: WebSocket): void {
+ const addonTerminal = term;
+ addonTerminal.off('data', addonTerminal.__sendData);
- socket = (typeof socket === 'undefined') ? term.socket : socket;
+ socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
if (socket) {
- socket.removeEventListener('message', term._getMessage);
+ socket.removeEventListener('message', addonTerminal.__getMessage);
}
- delete term.socket;
+ delete addonTerminal.__socket;
}
-export function apply(terminalConstructor: any): void {
+export function apply(terminalConstructor: typeof Terminal): void {
/**
* Attaches the current terminal to the given socket
*
- * @param {WebSocket} socket - The socket to attach the current terminal.
- * @param {boolean} bidirectional - Whether the terminal should send data
- * to the socket as well.
- * @param {boolean} buffered - Whether the rendering of incoming data
- * should happen instantly or at a maximum
- * frequency of 1 rendering per 10ms.
+ * @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 {
+ (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 {WebSocket} socket - The socket from which to detach the current
- * terminal.
+ * @param socket The socket from which to detach the current terminal.
*/
- terminalConstructor.prototype.detach = function (socket: WebSocket): void {
+ (terminalConstructor.prototype).detach = function (socket: WebSocket): void {
detach(this, socket);
};
}
diff --git a/src/addons/fit/fit.test.ts b/src/addons/fit/fit.test.ts
index 0dc17963..9a6d89fd 100644
--- a/src/addons/fit/fit.test.ts
+++ b/src/addons/fit/fit.test.ts
@@ -12,7 +12,7 @@ class MockTerminal {}
describe('fit addon', () => {
describe('apply', () => {
it('should do register the `proposeGeometry` and `fit` methods', () => {
- fit.apply(MockTerminal);
+ fit.apply(MockTerminal);
assert.equal(typeof (MockTerminal).prototype.proposeGeometry, 'function');
assert.equal(typeof (MockTerminal).prototype.fit, 'function');
});
diff --git a/src/addons/fit/fit.ts b/src/addons/fit/fit.ts
index 63111094..75e854d9 100644
--- a/src/addons/fit/fit.ts
+++ b/src/addons/fit/fit.ts
@@ -13,12 +13,16 @@
* row and truncate its width with the current number of columns).
*/
+///
+
+import { Terminal } from 'xterm';
+
export interface IGeometry {
rows: number;
cols: number;
}
-export function proposeGeometry(term: any): IGeometry {
+export function proposeGeometry(term: Terminal): IGeometry {
if (!term.element.parentElement) {
return null;
}
@@ -31,30 +35,30 @@ export function proposeGeometry(term: any): IGeometry {
const availableHeight = parentElementHeight - elementPaddingVer;
const availableWidth = parentElementWidth - elementPaddingHor;
const geometry = {
- cols: Math.floor(availableWidth / term.renderer.dimensions.actualCellWidth),
- rows: Math.floor(availableHeight / term.renderer.dimensions.actualCellHeight)
+ cols: Math.floor(availableWidth / (term).renderer.dimensions.actualCellWidth),
+ rows: Math.floor(availableHeight / (term).renderer.dimensions.actualCellHeight)
};
return geometry;
}
-export function fit(term: any): void {
+export function fit(term: Terminal): void {
const geometry = proposeGeometry(term);
if (geometry) {
// Force a full render
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
- term.renderer.clear();
+ (term).renderer.clear();
term.resize(geometry.cols, geometry.rows);
}
}
}
-export function apply(terminalConstructor: any): void {
- terminalConstructor.prototype.proposeGeometry = function (): IGeometry {
+export function apply(terminalConstructor: typeof Terminal): void {
+ (terminalConstructor.prototype).proposeGeometry = function (): IGeometry {
return proposeGeometry(this);
};
- terminalConstructor.prototype.fit = function (): void {
+ (terminalConstructor.prototype).fit = function (): void {
fit(this);
};
}
diff --git a/src/addons/fullscreen/fullscreen.test.ts b/src/addons/fullscreen/fullscreen.test.ts
index 9326ac4e..bb98bd30 100644
--- a/src/addons/fullscreen/fullscreen.test.ts
+++ b/src/addons/fullscreen/fullscreen.test.ts
@@ -12,7 +12,7 @@ class MockTerminal {}
describe('fullscreen addon', () => {
describe('apply', () => {
it('should do register the `toggleFullscreen` method', () => {
- fullscreen.apply(MockTerminal);
+ fullscreen.apply(MockTerminal);
assert.equal(typeof (MockTerminal).prototype.toggleFullScreen, 'function');
});
});
diff --git a/src/addons/fullscreen/fullscreen.ts b/src/addons/fullscreen/fullscreen.ts
index 2e5b72e1..47440667 100644
--- a/src/addons/fullscreen/fullscreen.ts
+++ b/src/addons/fullscreen/fullscreen.ts
@@ -3,13 +3,17 @@
* @license MIT
*/
+///
+
+import { Terminal } from 'xterm';
+
/**
* Toggle the given terminal's fullscreen mode.
- * @param {Terminal} term - The terminal to toggle full screen mode
- * @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false)
+ * @param term The terminal to toggle full screen mode
+ * @param fullscreen Toggle fullscreen on (true) or off (false)
*/
-export function toggleFullScreen(term: any, fullscreen: boolean): void {
- let fn;
+export function toggleFullScreen(term: Terminal, fullscreen: boolean): void {
+ let fn: string;
if (typeof fullscreen === 'undefined') {
fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';
@@ -22,8 +26,8 @@ export function toggleFullScreen(term: any, fullscreen: boolean): void {
term.element.classList[fn]('fullscreen');
}
-export function apply(terminalConstructor: any): void {
- terminalConstructor.prototype.toggleFullScreen = function (fullscreen: boolean): void {
+export function apply(terminalConstructor: typeof Terminal): void {
+ (terminalConstructor.prototype).toggleFullScreen = function (fullscreen: boolean): void {
toggleFullScreen(this, fullscreen);
};
}
diff --git a/src/addons/search/Interfaces.ts b/src/addons/search/Interfaces.ts
new file mode 100644
index 00000000..6faa03e2
--- /dev/null
+++ b/src/addons/search/Interfaces.ts
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { Terminal } from 'xterm';
+
+export interface ISearchAddonTerminal extends Terminal {
+ __searchHelper?: ISearchHelper;
+
+ // TODO: Reuse ITerminal from core
+ buffer: any;
+ selectionManager: any;
+}
+
+export interface ISearchHelper {
+ findNext(term: string): boolean;
+ findPrevious(term: string): boolean;
+}
diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts
index 5735dd48..2abc3b38 100644
--- a/src/addons/search/SearchHelper.ts
+++ b/src/addons/search/SearchHelper.ts
@@ -3,8 +3,7 @@
* @license MIT
*/
-// import { ITerminal } from '../../Interfaces';
-// import { translateBufferLineToString } from '../../utils/BufferLine';
+import { ISearchHelper, ISearchAddonTerminal } from './Interfaces';
interface ISearchResult {
term: string;
@@ -15,8 +14,8 @@ interface ISearchResult {
/**
* A class that knows how to search the terminal and how to display the results.
*/
-export class SearchHelper {
- constructor(private _terminal: any) {
+export class SearchHelper implements ISearchHelper {
+ constructor(private _terminal: ISearchAddonTerminal) {
// TODO: Search for multiple instances on 1 line
// TODO: Don't use the actual selection, instead use a "find selection" so multiple instances can be highlighted
// TODO: Highlight other instances in the viewport
@@ -114,8 +113,23 @@ export class SearchHelper {
private _findInLine(term: string, y: number): ISearchResult {
const lowerStringLine = this._terminal.buffer.translateBufferLineToString(y, true).toLowerCase();
const lowerTerm = term.toLowerCase();
- const searchIndex = lowerStringLine.indexOf(lowerTerm);
+ let searchIndex = lowerStringLine.indexOf(lowerTerm);
if (searchIndex >= 0) {
+ const line = this._terminal.buffer.lines.get(y);
+ for (let i = 0; i < searchIndex; i++) {
+ const charData = line[i];
+ // Adjust the searchIndex to normalize emoji into single chars
+ const char = charData[1/*CHAR_DATA_CHAR_INDEX*/];
+ if (char.length > 1) {
+ searchIndex -= char.length - 1;
+ }
+ // Adjust the searchIndex for empty characters following wide unicode
+ // chars (eg. CJK)
+ const charWidth = charData[2/*CHAR_DATA_WIDTH_INDEX*/];
+ if (charWidth === 0) {
+ searchIndex++;
+ }
+ }
return {
term,
col: searchIndex,
@@ -134,7 +148,7 @@ export class SearchHelper {
return false;
}
this._terminal.selectionManager.setSelection(result.col, result.row, result.term.length);
- this._terminal.scrollLines(result.row - this._terminal.buffer.ydisp, false);
+ this._terminal.scrollLines(result.row - this._terminal.buffer.ydisp);
return true;
}
}
diff --git a/src/addons/search/search.ts b/src/addons/search/search.ts
index 473c78fc..5c0b2b96 100644
--- a/src/addons/search/search.ts
+++ b/src/addons/search/search.ts
@@ -3,8 +3,11 @@
* @license MIT
*/
-import { SearchHelper } from './SearchHelper';
+///
+import { SearchHelper } from './SearchHelper';
+import { Terminal } from 'xterm';
+import { ISearchAddonTerminal } from './Interfaces';
/**
* Find the next instance of the term, then scroll to and select it. If it
@@ -12,11 +15,12 @@ import { SearchHelper } from './SearchHelper';
* @param term Tne search term.
* @return Whether a result was found.
*/
-export function findNext(terminal: any, term: string): boolean {
- if (!terminal._searchHelper) {
- terminal.searchHelper = new SearchHelper(terminal);
+export function findNext(terminal: Terminal, term: string): boolean {
+ const addonTerminal = terminal;
+ if (!addonTerminal.__searchHelper) {
+ addonTerminal.__searchHelper = new SearchHelper(addonTerminal);
}
- return (terminal.searchHelper).findNext(term);
+ return addonTerminal.__searchHelper.findNext(term);
}
/**
@@ -25,19 +29,20 @@ export function findNext(terminal: any, term: string): boolean {
* @param term Tne search term.
* @return Whether a result was found.
*/
-export function findPrevious(terminal: any, term: string): boolean {
- if (!terminal._searchHelper) {
- terminal.searchHelper = new SearchHelper(terminal);
+export function findPrevious(terminal: Terminal, term: string): boolean {
+ const addonTerminal = terminal;
+ if (!addonTerminal.__searchHelper) {
+ addonTerminal.__searchHelper = new SearchHelper(addonTerminal);
}
- return (terminal.searchHelper).findPrevious(term);
+ return addonTerminal.__searchHelper.findPrevious(term);
}
-export function apply(terminalConstructor: any): void {
- terminalConstructor.prototype.findNext = function(term: any): boolean {
+export function apply(terminalConstructor: typeof Terminal): void {
+ (terminalConstructor.prototype).findNext = function(term: string): boolean {
return findNext(this, term);
};
- terminalConstructor.prototype.findPrevious = function(term: any): boolean {
+ (terminalConstructor.prototype).findPrevious = function(term: string): boolean {
return findPrevious(this, term);
};
}
diff --git a/src/addons/terminado/Interfaces.ts b/src/addons/terminado/Interfaces.ts
new file mode 100644
index 00000000..8f17b0cc
--- /dev/null
+++ b/src/addons/terminado/Interfaces.ts
@@ -0,0 +1,19 @@
+/**
+ * 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 } from 'xterm';
+
+export interface ITerminadoAddonTerminal extends Terminal {
+ __socket?: WebSocket;
+ __attachSocketBuffer?: string;
+
+ __getMessage?(ev: MessageEvent): void;
+ __flushBuffer?(): void;
+ __pushToBuffer?(data: string): void;
+ __sendData?(data: string): void;
+ __setSize?(size: {rows: number, cols: number}): void;
+}
diff --git a/src/addons/terminado/terminado.test.ts b/src/addons/terminado/terminado.test.ts
index e258bd7c..2e4a53c5 100644
--- a/src/addons/terminado/terminado.test.ts
+++ b/src/addons/terminado/terminado.test.ts
@@ -12,7 +12,7 @@ class MockTerminal {}
describe('terminado addon', () => {
describe('apply', () => {
it('should do register the `terminadoAttach` and `terminadoDetach` methods', () => {
- terminado.apply(MockTerminal);
+ terminado.apply(MockTerminal);
assert.equal(typeof (MockTerminal).prototype.terminadoAttach, 'function');
assert.equal(typeof (MockTerminal).prototype.terminadoDetach, 'function');
});
diff --git a/src/addons/terminado/terminado.ts b/src/addons/terminado/terminado.ts
index e703198f..4acf9dd8 100644
--- a/src/addons/terminado/terminado.ts
+++ b/src/addons/terminado/terminado.ts
@@ -6,106 +6,107 @@
* WebSocket stream.
*/
+///
+
+import { Terminal } from 'xterm';
+import { ITerminadoAddonTerminal } from './Interfaces';
+
/**
* Attaches the given terminal to the given socket.
*
- * @param {Terminal} term - The terminal to be attached to the given socket.
- * @param {WebSocket} socket - The socket to attach the current terminal.
- * @param {boolean} bidirectional - Whether the terminal should send data
- * to the socket as well.
- * @param {boolean} buffered - Whether the rendering of incoming data
- * should happen instantly or at a maximum
- * frequency of 1 rendering per 10ms.
+ * @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 terminadoAttach(term: any, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
+export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
+ const addonTerminal = term;
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
- term.socket = socket;
+ addonTerminal.__socket = socket;
- term._flushBuffer = () => {
- term.write(term._attachSocketBuffer);
- term._attachSocketBuffer = null;
+ addonTerminal.__flushBuffer = () => {
+ addonTerminal.write(addonTerminal.__attachSocketBuffer);
+ addonTerminal.__attachSocketBuffer = null;
};
- term._pushToBuffer = (data) => {
- if (term._attachSocketBuffer) {
- term._attachSocketBuffer += data;
+ addonTerminal.__pushToBuffer = (data: string) => {
+ if (addonTerminal.__attachSocketBuffer) {
+ addonTerminal.__attachSocketBuffer += data;
} else {
- term._attachSocketBuffer = data;
- setTimeout(term._flushBuffer, 10);
+ addonTerminal.__attachSocketBuffer = data;
+ setTimeout(addonTerminal.__flushBuffer, 10);
}
};
- term._getMessage = (ev: MessageEvent) => {
+ addonTerminal.__getMessage = (ev: MessageEvent) => {
const data = JSON.parse(ev.data);
if (data[0] === 'stdout') {
if (buffered) {
- term._pushToBuffer(data[1]);
+ addonTerminal.__pushToBuffer(data[1]);
} else {
- term.write(data[1]);
+ addonTerminal.write(data[1]);
}
}
};
- term._sendData = (data: string) => {
+ addonTerminal.__sendData = (data: string) => {
socket.send(JSON.stringify(['stdin', data]));
};
- term._setSize = (size: {rows: number, cols: number}) => {
+ addonTerminal.__setSize = (size: {rows: number, cols: number}) => {
socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
};
- socket.addEventListener('message', term._getMessage);
+ socket.addEventListener('message', addonTerminal.__getMessage);
if (bidirectional) {
- term.on('data', term._sendData);
+ addonTerminal.on('data', addonTerminal.__sendData);
}
- term.on('resize', term._setSize);
+ addonTerminal.on('resize', addonTerminal.__setSize);
- socket.addEventListener('close', term.terminadoDetach.bind(term, socket));
- socket.addEventListener('error', term.terminadoDetach.bind(term, socket));
+ socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket));
+ socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket));
}
/**
* Detaches the given terminal from the given socket
*
- * @param {Xterm} term - The terminal to be detached from the given socket.
- * @param {WebSocket} socket - The socket from which to detach the current
- * terminal.
+ * @param term The terminal to be detached from the given socket.
+ * @param socket The socket from which to detach the current terminal.
*/
-export function terminadoDetach(term: any, socket: WebSocket): void {
- term.off('data', term._sendData);
+export function terminadoDetach(term: Terminal, socket: WebSocket): void {
+ const addonTerminal = term;
+ addonTerminal.off('data', addonTerminal.__sendData);
- socket = (typeof socket === 'undefined') ? term.socket : socket;
+ socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
if (socket) {
- socket.removeEventListener('message', term._getMessage);
+ socket.removeEventListener('message', addonTerminal.__getMessage);
}
- delete term.socket;
+ delete addonTerminal.__socket;
}
-export function apply(terminalConstructor: any): void {
+export function apply(terminalConstructor: typeof Terminal): void {
/**
* Attaches the current terminal to the given socket
*
- * @param {WebSocket} socket - The socket to attach the current terminal.
- * @param {boolean} bidirectional - Whether the terminal should send data
- * to the socket as well.
- * @param {boolean} buffered - Whether the rendering of incoming data
- * should happen instantly or at a maximum
- * frequency of 1 rendering per 10ms.
+ * @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.terminadoAttach = function(socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
+ (terminalConstructor.prototype).terminadoAttach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
return terminadoAttach(this, socket, bidirectional, buffered);
};
/**
* Detaches the current terminal from the given socket.
*
- * @param {WebSocket} socket - The socket from which to detach the current
- * terminal.
+ * @param socket The socket from which to detach the current terminal.
*/
- terminalConstructor.prototype.terminadoDetach = function(socket: WebSocket): void {
+ (terminalConstructor.prototype).terminadoDetach = function (socket: WebSocket): void {
return terminadoDetach(this, socket);
};
}
diff --git a/src/addons/winptyCompat/Interfaces.ts b/src/addons/winptyCompat/Interfaces.ts
new file mode 100644
index 00000000..8e02c64b
--- /dev/null
+++ b/src/addons/winptyCompat/Interfaces.ts
@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { Terminal } from 'xterm';
+
+export interface IWinptyCompatAddonTerminal extends Terminal {
+ buffer: any;
+}
diff --git a/src/addons/winptyCompat/winptyCompat.test.ts b/src/addons/winptyCompat/winptyCompat.test.ts
index 73761fc9..0c9269ed 100644
--- a/src/addons/winptyCompat/winptyCompat.test.ts
+++ b/src/addons/winptyCompat/winptyCompat.test.ts
@@ -12,7 +12,7 @@ class MockTerminal {}
describe('winptyCompat addon', () => {
describe('apply', () => {
it('should do register the `winptyCompatInit` method', () => {
- winptyCompat.apply(MockTerminal);
+ winptyCompat.apply(MockTerminal);
assert.equal(typeof (MockTerminal).prototype.winptyCompatInit, 'function');
});
});
diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts
index b594355b..61524e12 100644
--- a/src/addons/winptyCompat/winptyCompat.ts
+++ b/src/addons/winptyCompat/winptyCompat.ts
@@ -3,36 +3,43 @@
* @license MIT
*/
-export function winptyCompatInit(terminal: any): void {
- // Don't do anything when the platform is not Windows
- const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
- if (!isWindows) {
- return;
+///
+
+import { Terminal } from 'xterm';
+import { IWinptyCompatAddonTerminal } from './Interfaces';
+
+export function winptyCompatInit(terminal: Terminal): void {
+ const addonTerminal = terminal;
+
+ // Don't do anything when the platform is not Windows
+ const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
+ if (!isWindows) {
+ return;
+ }
+
+ // Winpty does not support wraparound mode which means that lines will never
+ // be marked as wrapped. This causes issues for things like copying a line
+ // retaining the wrapped new line characters or if consumers are listening
+ // in on the data stream.
+ //
+ // The workaround for this is to listen to every incoming line feed and mark
+ // the line as wrapped if the last character in the previous line is not a
+ // space. This is certainly not without its problems, but generally on
+ // Windows when text reaches the end of the terminal it's likely going to be
+ // wrapped.
+ addonTerminal.on('linefeed', () => {
+ const line = addonTerminal.buffer.lines.get(addonTerminal.buffer.ybase + addonTerminal.buffer.y - 1);
+ const lastChar = line[addonTerminal.cols - 1];
+
+ if (lastChar[3] !== 32 /* ' ' */) {
+ const nextLine = addonTerminal.buffer.lines.get(addonTerminal.buffer.ybase + addonTerminal.buffer.y);
+ (nextLine).isWrapped = true;
}
-
- // Winpty does not support wraparound mode which means that lines will never
- // be marked as wrapped. This causes issues for things like copying a line
- // retaining the wrapped new line characters or if consumers are listening
- // in on the data stream.
- //
- // The workaround for this is to listen to every incoming line feed and mark
- // the line as wrapped if the last character in the previous line is not a
- // space. This is certainly not without its problems, but generally on
- // Windows when text reaches the end of the terminal it's likely going to be
- // wrapped.
- terminal.on('linefeed', () => {
- const line = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y - 1);
- const lastChar = line[terminal.cols - 1];
-
- if (lastChar[3] !== 32 /* ' ' */) {
- const nextLine = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y);
- (nextLine).isWrapped = true;
- }
- });
+ });
}
-export function apply(terminalConstructor: any): void {
- terminalConstructor.prototype.winptyCompatInit = function(): void {
+export function apply(terminalConstructor: typeof Terminal): void {
+ (terminalConstructor.prototype).winptyCompatInit = function (): void {
winptyCompatInit(this);
};
}
diff --git a/src/addons/zmodem/zmodem.test.ts b/src/addons/zmodem/zmodem.test.ts
index 7646b7cc..682e62c8 100644
--- a/src/addons/zmodem/zmodem.test.ts
+++ b/src/addons/zmodem/zmodem.test.ts
@@ -12,7 +12,7 @@ class MockTerminal {}
describe('zmodem addon', () => {
describe('apply', () => {
it('should do register the `zmodemAttach` method and `zmodemBrowser` attribute', () => {
- zmodem.apply(MockTerminal);
+ zmodem.apply(MockTerminal);
assert.equal(typeof (MockTerminal).prototype.zmodemAttach, 'function');
assert.equal(typeof (MockTerminal).prototype.zmodemBrowser, 'object');
});
diff --git a/src/addons/zmodem/zmodem.ts b/src/addons/zmodem/zmodem.ts
index 4713f111..e8a7824d 100644
--- a/src/addons/zmodem/zmodem.ts
+++ b/src/addons/zmodem/zmodem.ts
@@ -1,3 +1,12 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+///
+
+import { Terminal } from 'xterm';
+
/**
*
* Allow xterm.js to handle ZMODEM uploads and downloads.
@@ -33,7 +42,7 @@ export interface IZModemOptions {
noTerminalWriteOutsideSession?: boolean;
}
-export function zmodemAttach(term: any, ws: WebSocket, opts: IZModemOptions = {}): void {
+export function zmodemAttach(term: Terminal, ws: WebSocket, opts: IZModemOptions = {}): void {
const senderFunc = (octets: ArrayLike) => ws.send(new Uint8Array(octets));
let zsentry;
@@ -51,8 +60,8 @@ export function zmodemAttach(term: any, ws: WebSocket, opts: IZModemOptions = {}
}
},
sender: senderFunc,
- on_retract: () => term.emit('zmodemRetract'),
- on_detect: (detection: any) => term.emit('zmodemDetect', detection)
+ on_retract: () => (term).emit('zmodemRetract'),
+ on_detect: (detection: any) => (term).emit('zmodemDetect', detection)
});
function handleWSMessage(evt: MessageEvent): void {
@@ -75,9 +84,9 @@ export function zmodemAttach(term: any, ws: WebSocket, opts: IZModemOptions = {}
ws.addEventListener('message', handleWSMessage);
}
-export function apply(terminalConstructor: any): void {
+export function apply(terminalConstructor: typeof Terminal): void {
zmodem = (typeof window === 'object') ? (window).ZModem : {Browser: null}; // Nullify browser for tests
- terminalConstructor.prototype.zmodemAttach = zmodemAttach.bind(this, this);
- terminalConstructor.prototype.zmodemBrowser = zmodem.Browser;
+ (terminalConstructor.prototype).zmodemAttach = zmodemAttach.bind(this, this);
+ (terminalConstructor.prototype).zmodemBrowser = zmodem.Browser;
}
diff --git a/src/handlers/Clipboard.ts b/src/handlers/Clipboard.ts
index b3277bd7..c0aa223d 100644
--- a/src/handlers/Clipboard.ts
+++ b/src/handlers/Clipboard.ts
@@ -3,7 +3,7 @@
* @license MIT
*/
-import { ITerminal, ISelectionManager } from '../Interfaces';
+import { ITerminal, ISelectionManager } from '../Types';
interface IWindow extends Window {
clipboardData?: {
diff --git a/src/input/MouseZoneManager.ts b/src/input/MouseZoneManager.ts
index 6fbe1c67..98377f36 100644
--- a/src/input/MouseZoneManager.ts
+++ b/src/input/MouseZoneManager.ts
@@ -3,8 +3,8 @@
* @license MIT
*/
-import { IMouseZoneManager, IMouseZone } from './Interfaces';
-import { ITerminal } from '../Interfaces';
+import { ITerminal } from '../Types';
+import { IMouseZoneManager, IMouseZone } from './Types';
const HOVER_DURATION = 500;
diff --git a/src/input/Interfaces.ts b/src/input/Types.ts
similarity index 100%
rename from src/input/Interfaces.ts
rename to src/input/Types.ts
diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts
index b4becbce..20a41cfa 100644
--- a/src/renderer/BaseRenderLayer.ts
+++ b/src/renderer/BaseRenderLayer.ts
@@ -3,10 +3,9 @@
* @license MIT
*/
-import { IRenderLayer, IColorSet, IRenderDimensions } from './Interfaces';
-import { ITerminal, ITerminalOptions } from '../Interfaces';
+import { IRenderLayer, IColorSet, IRenderDimensions } from './Types';
+import { CharData, ITerminal, ITerminalOptions } from '../Types';
import { acquireCharAtlas, CHAR_ATLAS_CELL_SPACING } from './CharAtlas';
-import { CharData } from '../Types';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
export const INVERTED_DEFAULT_COLOR = -1;
@@ -201,7 +200,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
* @param color The color of the character.
*/
protected fillCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number): void {
- this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
+ this._ctx.font = this._getFont(terminal, false);
this._ctx.textBaseline = 'top';
this._clipRow(terminal, y);
this._ctx.fillText(
@@ -269,7 +268,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
charAtlasCellWidth,
this._scaledCharHeight);
} else {
- this._drawUncachedChar(terminal, char, width, fg, x, y, bold, dim);
+ this._drawUncachedChar(terminal, char, width, fg, x, y, bold && terminal.options.enableBold, dim);
}
// This draws the atlas (for debugging purposes)
// this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
@@ -289,10 +288,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
private _drawUncachedChar(terminal: ITerminal, char: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean): void {
this._ctx.save();
- this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
- if (bold && terminal.options.enableBold) {
- this._ctx.font = `bold ${this._ctx.font}`;
- }
+ this._ctx.font = this._getFont(terminal, bold);
this._ctx.textBaseline = 'top';
if (fg === INVERTED_DEFAULT_COLOR) {
@@ -332,5 +328,16 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._scaledCellHeight);
this._ctx.clip();
}
+
+ /**
+ * Gets the current font.
+ * @param terminal The terminal.
+ * @param isBold If we should use the bold fontWeight.
+ */
+ protected _getFont(terminal: ITerminal, isBold: boolean): string {
+ const fontWeight = isBold ? terminal.options.fontWeightBold : terminal.options.fontWeight;
+
+ return `${fontWeight} ${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
+ }
}
diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts
index 9ac1e161..05cf7201 100644
--- a/src/renderer/CharAtlas.ts
+++ b/src/renderer/CharAtlas.ts
@@ -3,15 +3,18 @@
* @license MIT
*/
-import { ITerminal, ITheme } from '../Interfaces';
-import { IColorSet } from '../renderer/Interfaces';
-import { isFirefox } from '../utils/Browser';
+import { ITerminal } from '../Types';
+import { IColorSet } from './Types';
+import { isFirefox } from '../shared/utils/Browser';
+import { generateCharAtlas, ICharAtlasRequest } from '../shared/CharAtlasGenerator';
export const CHAR_ATLAS_CELL_SPACING = 1;
interface ICharAtlasConfig {
fontSize: number;
fontFamily: string;
+ fontWeight: string;
+ fontWeightBold: string;
scaledCharWidth: number;
scaledCharHeight: number;
colors: IColorSet;
@@ -63,8 +66,28 @@ export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledC
}
}
+ const canvasFactory = (width: number, height: number) => {
+ const canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+ return canvas;
+ };
+
+ const charAtlasConfig: ICharAtlasRequest = {
+ scaledCharWidth,
+ scaledCharHeight,
+ fontSize: terminal.options.fontSize,
+ fontFamily: terminal.options.fontFamily,
+ fontWeight: terminal.options.fontWeight,
+ fontWeightBold: terminal.options.fontWeightBold,
+ background: colors.background,
+ foreground: colors.foreground,
+ ansiColors: colors.ansi,
+ devicePixelRatio: window.devicePixelRatio
+ };
+
const newEntry: ICharAtlasCacheEntry = {
- bitmap: generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, colors.background, colors.foreground, colors.ansi),
+ bitmap: generateCharAtlas(window, canvasFactory, charAtlasConfig),
config: newConfig,
ownedBy: [terminal]
};
@@ -86,6 +109,8 @@ function generateConfig(scaledCharWidth: number, scaledCharHeight: number, termi
scaledCharHeight,
fontFamily: terminal.options.fontFamily,
fontSize: terminal.options.fontSize,
+ fontWeight: terminal.options.fontWeight,
+ fontWeightBold: terminal.options.fontWeightBold,
colors: clonedColors
};
}
@@ -98,125 +123,10 @@ function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {
}
return a.fontFamily === b.fontFamily &&
a.fontSize === b.fontSize &&
+ a.fontWeight === b.fontWeight &&
+ a.fontWeightBold === b.fontWeightBold &&
a.scaledCharWidth === b.scaledCharWidth &&
a.scaledCharHeight === b.scaledCharHeight &&
a.colors.foreground === b.colors.foreground &&
a.colors.background === b.colors.background;
}
-
-let generator: CharAtlasGenerator;
-
-/**
- * Initializes the char atlas generator.
- * @param document The document.
- */
-export function initialize(document: Document): void {
- if (!generator) {
- generator = new CharAtlasGenerator(document);
- }
-}
-
-class CharAtlasGenerator {
- private _canvas: HTMLCanvasElement;
- private _ctx: CanvasRenderingContext2D;
-
- constructor(private _document: Document) {
- this._canvas = this._document.createElement('canvas');
- this._ctx = this._canvas.getContext('2d', {alpha: false});
- this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
- }
-
- public generate(scaledCharWidth: number, scaledCharHeight: number, fontSize: number, fontFamily: string, background: string, foreground: string, ansiColors: string[]): HTMLCanvasElement | Promise {
- const cellWidth = scaledCharWidth + CHAR_ATLAS_CELL_SPACING;
- const cellHeight = scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
- this._canvas.width = 255 * cellWidth;
- this._canvas.height = (/*default+default bold*/2 + /*0-15*/16) * cellHeight;
-
- this._ctx.fillStyle = background;
- this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
-
- this._ctx.save();
- this._ctx.fillStyle = foreground;
- this._ctx.font = `${fontSize * window.devicePixelRatio}px ${fontFamily}`;
- this._ctx.textBaseline = 'top';
-
- // Default color
- for (let i = 0; i < 256; i++) {
- this._ctx.save();
- this._ctx.beginPath();
- this._ctx.rect(i * cellWidth, 0, cellWidth, cellHeight);
- this._ctx.clip();
- this._ctx.fillText(String.fromCharCode(i), i * cellWidth, 0);
- this._ctx.restore();
- }
- // Default color bold
- this._ctx.save();
- this._ctx.font = `bold ${this._ctx.font}`;
- for (let i = 0; i < 256; i++) {
- this._ctx.save();
- this._ctx.beginPath();
- this._ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight);
- this._ctx.clip();
- this._ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight);
- this._ctx.restore();
- }
- this._ctx.restore();
-
- // Colors 0-15
- this._ctx.font = `${fontSize * window.devicePixelRatio}px ${fontFamily}`;
- for (let colorIndex = 0; colorIndex < 16; colorIndex++) {
- // colors 8-15 are bold
- if (colorIndex === 8) {
- this._ctx.font = `bold ${this._ctx.font}`;
- }
- const y = (colorIndex + 2) * cellHeight;
- // Draw ascii characters
- for (let i = 0; i < 256; i++) {
- this._ctx.save();
- this._ctx.beginPath();
- this._ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
- this._ctx.clip();
- this._ctx.fillStyle = ansiColors[colorIndex];
- this._ctx.fillText(String.fromCharCode(i), i * cellWidth, y);
- this._ctx.restore();
- }
- }
- this._ctx.restore();
-
- // Support is patchy for createImageBitmap at the moment, pass a canvas back
- // if support is lacking as drawImage works there too. Firefox is also
- // included here as ImageBitmap appears both buggy and has horrible
- // performance (tested on v55).
- if (!('createImageBitmap' in window) || isFirefox) {
- // Regenerate canvas and context as they are now owned by the char atlas
- const result = this._canvas;
- this._canvas = this._document.createElement('canvas');
- this._ctx = this._canvas.getContext('2d');
- this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
- return result;
- }
-
- const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height);
-
- // Remove the background color from the image so characters may overlap
- const r = parseInt(background.substr(1, 2), 16);
- const g = parseInt(background.substr(3, 2), 16);
- const b = parseInt(background.substr(5, 2), 16);
- this._clearColor(charAtlasImageData, r, g, b);
-
- const promise = window.createImageBitmap(charAtlasImageData);
- // Clear the rect while the promise is in progress
- this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
- return promise;
- }
-
- private _clearColor(imageData: ImageData, r: number, g: number, b: number): void {
- for (let offset = 0; offset < imageData.data.length; offset += 4) {
- if (imageData.data[offset] === r &&
- imageData.data[offset + 1] === g &&
- imageData.data[offset + 2] === b) {
- imageData.data[offset + 3] = 0;
- }
- }
- }
-}
diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts
index 6c0d2945..b45c8646 100644
--- a/src/renderer/ColorManager.ts
+++ b/src/renderer/ColorManager.ts
@@ -3,8 +3,8 @@
* @license MIT
*/
-import { IColorSet, IColorManager } from './Interfaces';
-import { ITheme } from '../Interfaces';
+import { IColorSet, IColorManager } from './Types';
+import { ITheme } from 'xterm';
const DEFAULT_FOREGROUND = '#ffffff';
const DEFAULT_BACKGROUND = '#000000';
diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts
index 6ac489fc..d430b81d 100644
--- a/src/renderer/CursorRenderLayer.ts
+++ b/src/renderer/CursorRenderLayer.ts
@@ -3,13 +3,11 @@
* @license MIT
*/
-import { IColorSet, IRenderDimensions } from './Interfaces';
-import { IBuffer, ICharMeasure, ITerminal, ITerminalOptions } from '../Interfaces';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
import { GridCache } from './GridCache';
-import { FLAGS } from './Types';
+import { FLAGS, IColorSet, IRenderDimensions } from './Types';
import { BaseRenderLayer } from './BaseRenderLayer';
-import { CharData } from '../Types';
+import { CharData, IBuffer, ICharMeasure, ITerminal, ITerminalOptions } from '../Types';
interface ICursorState {
x: number;
diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts
deleted file mode 100644
index be1b39dd..00000000
--- a/src/renderer/Interfaces.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-/**
- * Copyright (c) 2017 The xterm.js authors. All rights reserved.
- * @license MIT
- */
-
-import { ITerminal, ITerminalOptions, ITheme, IEventEmitter } from '../Interfaces';
-
-export interface IRenderer extends IEventEmitter {
- dimensions: IRenderDimensions;
- colorManager: IColorManager;
-
- setTheme(theme: ITheme): IColorSet;
- onWindowResize(devicePixelRatio: number): void;
- onResize(cols: number, rows: number, didCharSizeChange: boolean): void;
- onCharSizeChanged(): void;
- onBlur(): void;
- onFocus(): void;
- onSelectionChanged(start: [number, number], end: [number, number]): void;
- onCursorMove(): void;
- onOptionsChanged(): void;
- clear(): void;
- queueRefresh(start: number, end: number): void;
-}
-
-export interface IRenderLayer {
- /**
- * Called when the terminal loses focus.
- */
- onBlur(terminal: ITerminal): void;
-
- /**
- * * Called when the terminal gets focus.
- */
- onFocus(terminal: ITerminal): void;
-
- /**
- * Called when the cursor is moved.
- */
- onCursorMove(terminal: ITerminal): void;
-
- /**
- * Called when options change.
- */
- onOptionsChanged(terminal: ITerminal): void;
-
- /**
- * Called when the theme changes.
- */
- onThemeChanged(terminal: ITerminal, colorSet: IColorSet): void;
-
- /**
- * Called when the data in the grid has changed (or needs to be rendered
- * again).
- */
- onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void;
-
- /**
- * Calls when the selection changes.
- */
- onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void;
-
- /**
- * Resize the render layer.
- */
- resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void;
-
- /**
- * Clear the state of the render layer.
- */
- reset(terminal: ITerminal): void;
-}
-
-export interface IColorManager {
- colors: IColorSet;
-}
-
-export interface IColorSet {
- foreground: string;
- background: string;
- cursor: string;
- cursorAccent: string;
- selection: string;
- ansi: string[];
-}
-
-export interface IRenderDimensions {
- scaledCharWidth: number;
- scaledCharHeight: number;
- scaledCellWidth: number;
- scaledCellHeight: number;
- scaledCharLeft: number;
- scaledCharTop: number;
- scaledCanvasWidth: number;
- scaledCanvasHeight: number;
- canvasWidth: number;
- canvasHeight: number;
- actualCellWidth: number;
- actualCellHeight: number;
-}
diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts
index 87fbddbd..61352f15 100644
--- a/src/renderer/LinkRenderLayer.ts
+++ b/src/renderer/LinkRenderLayer.ts
@@ -3,13 +3,11 @@
* @license MIT
*/
-import { IColorSet, IRenderDimensions } from './Interfaces';
-import { IBuffer, ICharMeasure, ILinkHoverEvent, ITerminal, ILinkifierAccessor } from '../Interfaces';
+import { ILinkHoverEvent, ITerminal, ILinkifierAccessor, IBuffer, ICharMeasure, LinkHoverEventTypes } from '../Types';
import { CHAR_DATA_ATTR_INDEX } from '../Buffer';
import { GridCache } from './GridCache';
-import { FLAGS } from './Types';
+import { FLAGS, IColorSet, IRenderDimensions } from './Types';
import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer';
-import { LinkHoverEventTypes } from '../Types';
export class LinkRenderLayer extends BaseRenderLayer {
private _state: ILinkHoverEvent = null;
diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts
index 680d9490..8b9f5006 100644
--- a/src/renderer/Renderer.ts
+++ b/src/renderer/Renderer.ts
@@ -3,17 +3,18 @@
* @license MIT
*/
-import { ITerminal, ITheme } from '../Interfaces';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
import { TextRenderLayer } from './TextRenderLayer';
import { SelectionRenderLayer } from './SelectionRenderLayer';
import { CursorRenderLayer } from './CursorRenderLayer';
import { ColorManager } from './ColorManager';
import { BaseRenderLayer } from './BaseRenderLayer';
-import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions } from './Interfaces';
+import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions } from './Types';
+import { ITerminal } from '../Types';
import { LinkRenderLayer } from './LinkRenderLayer';
import { EventEmitter } from '../EventEmitter';
import { ScreenDprMonitor } from '../utils/ScreenDprMonitor';
+import { ITheme } from 'xterm';
export class Renderer extends EventEmitter implements IRenderer {
/** A queue of the rows to be refreshed */
diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts
index 9740bb9a..53fc9b39 100644
--- a/src/renderer/SelectionRenderLayer.ts
+++ b/src/renderer/SelectionRenderLayer.ts
@@ -3,11 +3,10 @@
* @license MIT
*/
-import { IColorSet, IRenderDimensions } from './Interfaces';
-import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces';
+import { IBuffer, ICharMeasure, ITerminal } from '../Types';
import { CHAR_DATA_ATTR_INDEX } from '../Buffer';
import { GridCache } from './GridCache';
-import { FLAGS } from './Types';
+import { FLAGS, IColorSet, IRenderDimensions } from './Types';
import { BaseRenderLayer } from './BaseRenderLayer';
export class SelectionRenderLayer extends BaseRenderLayer {
diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts
index 99a6feae..3c01cea3 100644
--- a/src/renderer/TextRenderLayer.ts
+++ b/src/renderer/TextRenderLayer.ts
@@ -3,12 +3,10 @@
* @license MIT
*/
-import { IColorSet, IRenderDimensions } from './Interfaces';
-import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces';
import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer';
-import { FLAGS } from './Types';
+import { FLAGS, IColorSet, IRenderDimensions } from './Types';
+import { CharData, IBuffer, ICharMeasure, ITerminal } from '../Types';
import { GridCache } from './GridCache';
-import { CharData } from '../Types';
import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer';
/**
@@ -33,7 +31,7 @@ export class TextRenderLayer extends BaseRenderLayer {
super.resize(terminal, dim, charSizeChanged);
// Clear the character width cache if the font or width has changed
- const terminalFont = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
+ const terminalFont = this._getFont(terminal, false);
if (this._characterWidth !== dim.scaledCharWidth || this._characterFont !== terminalFont) {
this._characterWidth = dim.scaledCharWidth;
this._characterFont = terminalFont;
@@ -166,7 +164,7 @@ export class TextRenderLayer extends BaseRenderLayer {
this._ctx.save();
if (flags & FLAGS.BOLD) {
- this._ctx.font = `bold ${this._ctx.font}`;
+ this._ctx.font = this._getFont(terminal, true);
// Convert the FG color to the bold variant
if (fg < 8) {
fg += 8;
diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts
index 834f8813..52c06e04 100644
--- a/src/renderer/Types.ts
+++ b/src/renderer/Types.ts
@@ -3,7 +3,10 @@
* @license MIT
*/
- /**
+import { ITerminal } from '../Types';
+import { IEventEmitter, ITheme } from 'xterm';
+
+/**
* Flags used to render terminal text properly.
*/
export enum FLAGS {
@@ -14,3 +17,96 @@ export enum FLAGS {
INVISIBLE = 16,
DIM = 32
}
+
+export interface IRenderer extends IEventEmitter {
+ dimensions: IRenderDimensions;
+ colorManager: IColorManager;
+
+ setTheme(theme: ITheme): IColorSet;
+ onWindowResize(devicePixelRatio: number): void;
+ onResize(cols: number, rows: number, didCharSizeChange: boolean): void;
+ onCharSizeChanged(): void;
+ onBlur(): void;
+ onFocus(): void;
+ onSelectionChanged(start: [number, number], end: [number, number]): void;
+ onCursorMove(): void;
+ onOptionsChanged(): void;
+ clear(): void;
+ queueRefresh(start: number, end: number): void;
+}
+
+export interface IColorManager {
+ colors: IColorSet;
+}
+
+export interface IColorSet {
+ foreground: string;
+ background: string;
+ cursor: string;
+ cursorAccent: string;
+ selection: string;
+ ansi: string[];
+}
+
+export interface IRenderDimensions {
+ scaledCharWidth: number;
+ scaledCharHeight: number;
+ scaledCellWidth: number;
+ scaledCellHeight: number;
+ scaledCharLeft: number;
+ scaledCharTop: number;
+ scaledCanvasWidth: number;
+ scaledCanvasHeight: number;
+ canvasWidth: number;
+ canvasHeight: number;
+ actualCellWidth: number;
+ actualCellHeight: number;
+}
+
+export interface IRenderLayer {
+ /**
+ * Called when the terminal loses focus.
+ */
+ onBlur(terminal: ITerminal): void;
+
+ /**
+ * * Called when the terminal gets focus.
+ */
+ onFocus(terminal: ITerminal): void;
+
+ /**
+ * Called when the cursor is moved.
+ */
+ onCursorMove(terminal: ITerminal): void;
+
+ /**
+ * Called when options change.
+ */
+ onOptionsChanged(terminal: ITerminal): void;
+
+ /**
+ * Called when the theme changes.
+ */
+ onThemeChanged(terminal: ITerminal, colorSet: IColorSet): void;
+
+ /**
+ * Called when the data in the grid has changed (or needs to be rendered
+ * again).
+ */
+ onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void;
+
+ /**
+ * Calls when the selection changes.
+ */
+ onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void;
+
+ /**
+ * Resize the render layer.
+ */
+ resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void;
+
+ /**
+ * Clear the state of the render layer.
+ */
+ reset(terminal: ITerminal): void;
+}
diff --git a/src/shared/CharAtlasGenerator.ts b/src/shared/CharAtlasGenerator.ts
new file mode 100644
index 00000000..9ae9f4b3
--- /dev/null
+++ b/src/shared/CharAtlasGenerator.ts
@@ -0,0 +1,140 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { FontWeight } from 'xterm';
+import { isFirefox } from './utils/Browser';
+
+declare const Promise: any;
+
+export interface IOffscreenCanvas {
+ width: number;
+ height: number;
+ getContext(type: '2d', config?: Canvas2DContextAttributes): CanvasRenderingContext2D;
+ transferToImageBitmap(): ImageBitmap;
+}
+
+export interface ICharAtlasRequest {
+ scaledCharWidth: number;
+ scaledCharHeight: number;
+ fontSize: number;
+ fontFamily: string;
+ fontWeight: FontWeight;
+ fontWeightBold: FontWeight;
+ background: string;
+ foreground: string;
+ ansiColors: string[];
+ devicePixelRatio: number;
+}
+
+export const CHAR_ATLAS_CELL_SPACING = 1;
+
+/**
+ * Generates a char atlas.
+ * @param context The window or worker context.
+ * @param canvasFactory A function to generate a canvas with a width or height.
+ * @param request The config for the new char atlas.
+ */
+export function generateCharAtlas(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement | IOffscreenCanvas, request: ICharAtlasRequest): HTMLCanvasElement | Promise {
+ const cellWidth = request.scaledCharWidth + CHAR_ATLAS_CELL_SPACING;
+ const cellHeight = request.scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
+ const canvas = canvasFactory(
+ /*255 ascii chars*/255 * cellWidth,
+ (/*default+default bold*/2 + /*0-15*/16) * cellHeight
+ );
+ const ctx = canvas.getContext('2d', {alpha: false});
+
+ ctx.fillStyle = request.background;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ ctx.save();
+ ctx.fillStyle = request.foreground;
+ ctx.font = getFont(request.fontWeight, request);
+ ctx.textBaseline = 'top';
+
+ // Default color
+ for (let i = 0; i < 256; i++) {
+ ctx.save();
+ ctx.beginPath();
+ ctx.rect(i * cellWidth, 0, cellWidth, cellHeight);
+ ctx.clip();
+ ctx.fillText(String.fromCharCode(i), i * cellWidth, 0);
+ ctx.restore();
+ }
+ // Default color bold
+ ctx.save();
+ ctx.font = getFont(request.fontWeightBold, request);
+ for (let i = 0; i < 256; i++) {
+ ctx.save();
+ ctx.beginPath();
+ ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight);
+ ctx.clip();
+ ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight);
+ ctx.restore();
+ }
+ ctx.restore();
+
+ // Colors 0-15
+ ctx.font = getFont(request.fontWeight, request);
+ for (let colorIndex = 0; colorIndex < 16; colorIndex++) {
+ // colors 8-15 are bold
+ if (colorIndex === 8) {
+ ctx.font = getFont(request.fontWeightBold, request);
+ }
+ const y = (colorIndex + 2) * cellHeight;
+ // Draw ascii characters
+ for (let i = 0; i < 256; i++) {
+ ctx.save();
+ ctx.beginPath();
+ ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
+ ctx.clip();
+ ctx.fillStyle = request.ansiColors[colorIndex];
+ ctx.fillText(String.fromCharCode(i), i * cellWidth, y);
+ ctx.restore();
+ }
+ }
+ ctx.restore();
+
+ // Support is patchy for createImageBitmap at the moment, pass a canvas back
+ // if support is lacking as drawImage works there too. Firefox is also
+ // included here as ImageBitmap appears both buggy and has horrible
+ // performance (tested on v55).
+ if (!('createImageBitmap' in context) || isFirefox) {
+ // Don't attempt to clear background colors if createImageBitmap is not supported
+ if (canvas instanceof HTMLCanvasElement) {
+ // Just return the HTMLCanvas if it's a HTMLCanvasElement
+ return canvas;
+ } else {
+ // Transfer to an ImageBitmap is this is an OffscreenCanvas
+ return new Promise(r => r(canvas.transferToImageBitmap()));
+ }
+ }
+
+ const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+
+ // Remove the background color from the image so characters may overlap
+ const r = parseInt(request.background.substr(1, 2), 16);
+ const g = parseInt(request.background.substr(3, 2), 16);
+ const b = parseInt(request.background.substr(5, 2), 16);
+ clearColor(charAtlasImageData, r, g, b);
+
+ return context.createImageBitmap(charAtlasImageData);
+}
+
+/**
+ * Makes a partiicular rgb color in an ImageData completely transparent.
+ */
+function clearColor(imageData: ImageData, r: number, g: number, b: number): void {
+ for (let offset = 0; offset < imageData.data.length; offset += 4) {
+ if (imageData.data[offset] === r &&
+ imageData.data[offset + 1] === g &&
+ imageData.data[offset + 2] === b) {
+ imageData.data[offset + 3] = 0;
+ }
+ }
+}
+
+function getFont(fontWeight: FontWeight, request: ICharAtlasRequest): string {
+ return `${fontWeight} ${request.fontSize * request.devicePixelRatio}px ${request.fontFamily}`;
+}
diff --git a/src/utils/Browser.ts b/src/shared/utils/Browser.ts
similarity index 76%
rename from src/utils/Browser.ts
rename to src/shared/utils/Browser.ts
index 48c0c374..be71d875 100644
--- a/src/utils/Browser.ts
+++ b/src/shared/utils/Browser.ts
@@ -3,8 +3,6 @@
* @license MIT
*/
-import { contains } from './Generic';
-
const isNode = (typeof navigator === 'undefined') ? true : false;
const userAgent = (isNode) ? 'node' : navigator.userAgent;
const platform = (isNode) ? 'node' : navigator.platform;
@@ -20,3 +18,12 @@ export const isIpad = platform === 'iPad';
export const isIphone = platform === 'iPhone';
export const isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform);
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
+ */
+function contains(arr: any[], el: any): boolean {
+ return arr.indexOf(el) >= 0;
+}
diff --git a/src/utils/CharMeasure.test.ts b/src/utils/CharMeasure.test.ts
index f50ce96b..e4f4e1d6 100644
--- a/src/utils/CharMeasure.test.ts
+++ b/src/utils/CharMeasure.test.ts
@@ -4,8 +4,8 @@
*/
import jsdom = require('jsdom');
+import { ICharMeasure, ITerminal } from '../Types';
import { assert } from 'chai';
-import { ICharMeasure, ITerminal } from '../Interfaces';
import { CharMeasure } from './CharMeasure';
describe('CharMeasure', () => {
diff --git a/src/utils/CharMeasure.ts b/src/utils/CharMeasure.ts
index 62291ab2..91cfce5f 100644
--- a/src/utils/CharMeasure.ts
+++ b/src/utils/CharMeasure.ts
@@ -3,8 +3,8 @@
* @license MIT
*/
+import { ICharMeasure, ITerminal, ITerminalOptions } from '../Types';
import { EventEmitter } from '../EventEmitter';
-import { ICharMeasure, ITerminal, ITerminalOptions } from '../Interfaces';
/**
* Utility class that measures the size of a character. Measurements are done in
diff --git a/src/utils/CircularList.ts b/src/utils/CircularList.ts
index 97a32b79..6b74971b 100644
--- a/src/utils/CircularList.ts
+++ b/src/utils/CircularList.ts
@@ -4,7 +4,7 @@
*/
import { EventEmitter } from '../EventEmitter';
-import { ICircularList } from '../Interfaces';
+import { ICircularList } from '../Types';
/**
* Represents a circular list; a list with a maximum size that wraps around when push is called,
diff --git a/src/utils/Generic.ts b/src/utils/Generic.ts
deleted file mode 100644
index 4bc6c487..00000000
--- a/src/utils/Generic.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * Copyright (c) 2016 The xterm.js authors. All rights reserved.
- * @license MIT
- */
-
-/**
- * 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
- */
-export function contains(arr: any[], el: any): boolean {
- return arr.indexOf(el) >= 0;
-}
diff --git a/src/utils/MouseHelper.ts b/src/utils/MouseHelper.ts
index d7d8f698..f62593eb 100644
--- a/src/utils/MouseHelper.ts
+++ b/src/utils/MouseHelper.ts
@@ -3,8 +3,8 @@
* @license MIT
*/
-import { ICharMeasure } from '../Interfaces';
-import { IRenderer } from '../renderer/Interfaces';
+import { ICharMeasure } from '../Types';
+import { IRenderer } from '../renderer/Types';
export class MouseHelper {
constructor(private _renderer: IRenderer) {}
@@ -24,7 +24,7 @@ export class MouseHelper {
while (element) {
x -= element.offsetLeft;
y -= element.offsetTop;
- element = 'offsetParent' in element ? element.offsetParent : element.parentElement;
+ element = element.offsetParent;
}
element = originalElement;
while (element && element !== element.ownerDocument.body) {
diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts
index 80e25277..0744cd14 100644
--- a/src/utils/TestUtils.test.ts
+++ b/src/utils/TestUtils.test.ts
@@ -3,13 +3,73 @@
* @license MIT
*/
-import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType, IInputHandlingTerminal, IViewport, ICircularList, ICompositionHelper, ITheme, ILinkifier, IMouseHelper } from '../Interfaces';
-import { LineData } from '../Types';
+import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Types';
+import { LineData, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ICircularList, ILinkifier, IMouseHelper, ILinkMatcherOptions } from '../Types';
import { Buffer } from '../Buffer';
-import * as Browser from './Browser';
-import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Interfaces';
+import * as Browser from '../shared/utils/Browser';
+import { ITheme } from 'xterm';
export class MockTerminal implements ITerminal {
+ getOption(key: any): any {
+ throw new Error('Method not implemented.');
+ }
+ setOption(key: any, value: any): void {
+ throw new Error('Method not implemented.');
+ }
+ blur(): void {
+ throw new Error('Method not implemented.');
+ }
+ focus(): void {
+ throw new Error('Method not implemented.');
+ }
+ resize(columns: number, rows: number): void {
+ throw new Error('Method not implemented.');
+ }
+ writeln(data: string): void {
+ throw new Error('Method not implemented.');
+ }
+ open(parent: HTMLElement): void {
+ throw new Error('Method not implemented.');
+ }
+ attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
+ throw new Error('Method not implemented.');
+ }
+ registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number {
+ throw new Error('Method not implemented.');
+ }
+ deregisterLinkMatcher(matcherId: number): void {
+ throw new Error('Method not implemented.');
+ }
+ hasSelection(): boolean {
+ throw new Error('Method not implemented.');
+ }
+ getSelection(): string {
+ throw new Error('Method not implemented.');
+ }
+ clearSelection(): void {
+ throw new Error('Method not implemented.');
+ }
+ selectAll(): void {
+ throw new Error('Method not implemented.');
+ }
+ destroy(): void {
+ throw new Error('Method not implemented.');
+ }
+ scrollPages(pageCount: number): void {
+ throw new Error('Method not implemented.');
+ }
+ scrollToTop(): void {
+ throw new Error('Method not implemented.');
+ }
+ scrollToBottom(): void {
+ throw new Error('Method not implemented.');
+ }
+ clear(): void {
+ throw new Error('Method not implemented.');
+ }
+ write(data: string): void {
+ throw new Error('Method not implemented.');
+ }
bracketedPasteMode: boolean;
mouseHelper: IMouseHelper;
renderer: IRenderer;
@@ -39,7 +99,7 @@ export class MockTerminal implements ITerminal {
on(event: string, callback: () => void): void {
throw new Error('Method not implemented.');
}
- off(type: string, listener: IListenerType): void {
+ off(type: string, listener: (...args: any[]) => void): void {
throw new Error('Method not implemented.');
}
scrollLines(disp: number, suppressScrollEvent: boolean): void {
@@ -184,10 +244,10 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
setOption(key: string, value: any): void {
this.options[key] = value;
}
- on(type: string, listener: IListenerType): void {
+ on(type: string, listener: (...args: any[]) => void): void {
throw new Error('Method not implemented.');
}
- off(type: string, listener: IListenerType): void {
+ off(type: string, listener: (...args: any[]) => void): void {
throw new Error('Method not implemented.');
}
emit(type: string, data?: any): void {
@@ -220,10 +280,10 @@ export class MockBuffer implements IBuffer {
export class MockRenderer implements IRenderer {
colorManager: IColorManager;
- on(type: string, listener: IListenerType): void {
+ on(type: string, listener: (...args: any[]) => void): void {
throw new Error('Method not implemented.');
}
- off(type: string, listener: IListenerType): void {
+ off(type: string, listener: (...args: any[]) => void): void {
throw new Error('Method not implemented.');
}
emit(type: string, data?: any): void {
diff --git a/tsconfig.json b/tsconfig.json
index a3aa790c..38bfd2fa 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,8 +5,7 @@
"rootDir": "src",
"outDir": "lib",
"sourceMap": true,
- "removeComments": true,
- "declaration": true
+ "removeComments": true
},
"include": [
"src/**/*"
diff --git a/tslint.json b/tslint.json
index b5367f6a..f6ad759a 100644
--- a/tslint.json
+++ b/tslint.json
@@ -24,6 +24,7 @@
"parameter"
],
"eofline": true,
+ "no-duplicate-imports": true,
"no-eval": true,
"no-internal-module": true,
"no-trailing-whitespace": true,
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index 09769dbe..72a51cf2 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -8,6 +8,11 @@
*/
declare module 'xterm' {
+ /**
+ * A string representing text font weight.
+ */
+ export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
+
/**
* An object containing start up options for the terminal.
*/
@@ -57,6 +62,16 @@ declare module 'xterm' {
*/
fontFamily?: string;
+ /**
+ * The font weight used to render non-bold text.
+ */
+ fontWeight?: FontWeight;
+
+ /**
+ * The font weight used to render bold text.
+ */
+ fontWeightBold?: FontWeight;
+
/**
* The spacing in whole pixels between characters..
*/
@@ -67,6 +82,17 @@ declare module 'xterm' {
*/
lineHeight?: number;
+ /**
+ * Whether to treat option as the meta key.
+ */
+ macOptionIsMeta?: boolean;
+
+ /**
+ * Whether to select the word under the cursor on right click, this is
+ * standard behavior in a lot of macOS applications.
+ */
+ rightClickSelectsWord?: boolean;
+
/**
* The number of rows in the terminal.
*/
@@ -172,10 +198,16 @@ declare module 'xterm' {
priority?: number;
}
+ export interface IEventEmitter {
+ on(type: string, listener: (...args: any[]) => void): void;
+ off(type: string, listener: (...args: any[]) => void): void;
+ emit(type: string, data?: any): void;
+ }
+
/**
* The class that represents an xterm.js terminal.
*/
- export class Terminal {
+ export class Terminal implements IEventEmitter {
/**
* The element containing the terminal.
*/
@@ -224,7 +256,7 @@ declare module 'xterm' {
* @param type The type of the event.
* @param listener The listener.
*/
- on(type: 'data', listener: (data?: string) => void): void;
+ on(type: 'data', listener: (...args: any[]) => void): void;
/**
* Registers an event listener.
* @param type The type of the event.
@@ -275,6 +307,8 @@ declare module 'xterm' {
*/
off(type: 'blur' | 'focus' | 'linefeed' | 'selection' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void;
+ emit(type: string, data?: any): void;
+
/**
* Resizes the terminal.
* @param x The number of columns to resize to.
@@ -346,22 +380,6 @@ declare module 'xterm' {
*/
selectAll(): void;
- // /**
- // * Find the next instance of the term, then scroll to and select it. If it
- // * doesn't exist, do nothing.
- // * @param term Tne search term.
- // * @return Whether a result was found.
- // */
- // findNext(term: string): boolean;
-
- // /**
- // * Find the previous instance of the term, then scroll to and select it. If it
- // * doesn't exist, do nothing.
- // * @param term Tne search term.
- // * @return Whether a result was found.
- // */
- // findPrevious(term: string): boolean;
-
/**
* Destroys the terminal and detaches it from the DOM.
*/
@@ -404,12 +422,12 @@ declare module 'xterm' {
* Retrieves an option's value from the terminal.
* @param key The option key.
*/
- getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'termName'): string;
+ getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold'| 'termName'): string;
/**
* Retrieves an option's value from the terminal.
* @param key The option key.
*/
- getOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'rightClickSelectsWord'): boolean;
+ getOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean;
/**
* Retrieves an option's value from the terminal.
* @param key The option key.
@@ -437,6 +455,12 @@ declare module 'xterm' {
* @param value The option value.
*/
setOption(key: 'fontFamily' | 'termName' | 'bellSound', value: string): void;
+ /**
+ * Sets an option on the terminal.
+ * @param key The option key.
+ * @param value The option value.
+ */
+ setOption(key: 'fontWeight' | 'fontWeightBold', value: null | 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'): void;
/**
* Sets an option on the terminal.
* @param key The option key.
@@ -454,7 +478,7 @@ declare module 'xterm' {
* @param key The option key.
* @param value The option value.
*/
- setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'rightClickSelectsWord', value: boolean): void;
+ setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void;
/**
* Sets an option on the terminal.
* @param key The option key.