Merge remote-tracking branch 'ups/master' into 955_worker_refactor

This commit is contained in:
Daniel Imms
2018-01-25 07:16:00 -08:00
65 changed files with 997 additions and 1244 deletions
-1
View File
@@ -1,7 +1,6 @@
language: node_js
os:
- linux
- osx
node_js:
- 6
before_install:
+5
View File
@@ -120,6 +120,11 @@ computational environment for Jupyter, supporting interactive data science and s
- [**VTerm**](https://github.com/vterm/vterm): Extensible terminal emulator based on Electron and React.
- [**electerm**](http://electerm.html5beta.com): electerm is a terminal/ssh/sftp client(mac, win, linux) based on electron/node-pty/xterm.
- [**Kubebox**](https://github.com/astefanutti/kubebox): Terminal console for Kubernetes clusters.
- [**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.
+3
View File
@@ -23,6 +23,9 @@
<p>
<label><input type="checkbox" id="option-cursor-blink"> cursorBlink</label>
</p>
<p>
<label><input type="checkbox" id="option-mac-option-is-meta"> macOptionIsMeta</label>
</p>
<p>
<label>
cursorStyle
+5
View File
@@ -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)
+8
View File
@@ -144,6 +144,9 @@ 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('macOptionIsMeta');
const r24: string = t.getOption('fontWeight');
const r25: string = t.getOption('fontWeightBold');
}
{
const t: Terminal = new Terminal();
@@ -157,6 +160,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);
@@ -177,6 +184,7 @@ namespace methods_core {
t.setOption('lineHeight', 1);
t.setOption('fontFamily', 'foo');
t.setOption('theme', {background: '#ff0000'});
t.setOption('macOptionIsMeta', true);
}
}
namespace scrolling {
+38 -46
View File
@@ -18,11 +18,13 @@ const ts = require('gulp-typescript');
const util = require('gulp-util');
const webpack = require('webpack-stream');
let buildDir = process.env.BUILD_DIR || 'build';
let tsProject = ts.createProject('tsconfig.json');
let srcDir = tsProject.config.compilerOptions.rootDir;
const buildDir = process.env.BUILD_DIR || 'build';
const tsProject = ts.createProject('tsconfig.json');
const srcDir = tsProject.config.compilerOptions.rootDir;
let outDir = tsProject.config.compilerOptions.outDir;
const addons = ['attach', 'fit', 'fullscreen', 'search', 'terminado', 'winptyCompat', 'zmodem'];
// Under some environments like TravisCI, this comes out at absolute which can
// break the build. This ensures that the outDir is absolute.
if (path.normalize(outDir).indexOf(__dirname) !== 0) {
@@ -45,15 +47,19 @@ gulp.task('tsc', function () {
tsResult.dts.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(outDir))
);
let addons = ['attach', 'fit', 'fullscreen', 'search', 'terminado', 'winptyCompat', 'zmodem'];
let addonStreams = addons.map(function(addon) {
fs.emptyDirSync(`${outDir}/addons/${addon}`);
let tsProjectAddon = ts.createProject(`./src/addons/${addon}/tsconfig.json`);
let tsResultAddon = tsProjectAddon.src().pipe(sourcemaps.init()).pipe(tsProjectAddon());
let tscAddon = tsResultAddon.js
.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''}))
.pipe(gulp.dest(`${outDir}/addons/${addon}`));
let tscAddon = merge(
tsResultAddon.js
.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''}))
.pipe(gulp.dest(`${outDir}/addons/${addon}`)),
tsResultAddon.dts
.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''}))
.pipe(gulp.dest(`${outDir}/addons/${addon}`))
)
return tscAddon;
});
@@ -103,45 +109,29 @@ gulp.task('browserify', ['tsc'], function() {
});
gulp.task('browserify-addons', ['tsc'], function() {
let searchOptions = {
basedir: `${buildDir}/addons/search`,
debug: true,
entries: [`${outDir}/addons/search/search.js`],
cache: {},
packageCache: {}
};
let searchBundle = browserify(searchOptions)
.external(path.join(outDir, 'Terminal.js'))
.bundle()
.pipe(source('./addons/search/search.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true, sourceRoot: ''}))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(buildDir));
const bundles = addons.map((addon) => {
const addonOptions = {
basedir: `${buildDir}/addons/${addon}`,
debug: true,
entries: [`${outDir}/addons/${addon}/${addon}.js`],
standalone: addon,
cache: {},
packageCache: {}
};
let winptyCompatOptions = {
basedir: `${buildDir}/addons/winptyCompat`,
debug: true,
entries: [`${outDir}/addons/winptyCompat/winptyCompat.js`],
cache: {},
packageCache: {}
};
let winptyCompatBundle = browserify(winptyCompatOptions)
.external(path.join(outDir, 'Terminal.js'))
.bundle()
.pipe(source('./addons/winptyCompat/winptyCompat.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true, sourceRoot: ''}))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(buildDir));
const addonBundle = browserify(addonOptions)
.external(path.join(outDir, 'Terminal.js'))
.bundle()
.pipe(source(`./addons/${addon}/${addon}.js`))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true, sourceRoot: ''}))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(buildDir));
// Copy all add-ons from outDir to buildDir
let copyAddons = gulp.src([
// Copy JS addons
`${outDir}/addons/**/*`
]).pipe(gulp.dest(`${buildDir}/addons`));
return addonBundle;
});
return merge(searchBundle, winptyCompatBundle, copyAddons);
return merge(...bundles);
});
gulp.task('instrument-test', function () {
@@ -189,9 +179,11 @@ gulp.task('sorcery', ['browserify'], function () {
});
gulp.task('sorcery-addons', ['browserify-addons'], function () {
var chain = sorcery.loadSync(`${buildDir}/addons/search/search.js`);
chain.apply();
chain.writeSync();
addons.forEach((addon) => {
const chain = sorcery.loadSync(`${buildDir}/addons/${addon}/${addon}.js`);
chain.apply();
chain.writeSync();
})
});
gulp.task('webpack', ['build'], function() {
+251 -759
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -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",
@@ -66,7 +66,7 @@
"node-pty": "^0.7.2",
"nodemon": "1.10.2",
"sorcery": "^0.10.0",
"tslint": "^4.0.2",
"tslint": "^5.9.1",
"typescript": "~2.4.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
+4 -12
View File
@@ -121,11 +121,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 +177,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;
}
+14 -3
View File
@@ -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
});
}
/**
+3 -3
View File
@@ -3,19 +3,19 @@
* @license MIT
*/
import { Charset } from './Types';
import { ICharset } from './Interfaces';
/**
* The character sets supported by the terminal. These enable several languages
* to be represented within the terminal with only 8-bit encoding. See ISO 2022
* for a discussion on character sets. Only VT100 character sets are supported.
*/
export const CHARSETS: { [key: string]: Charset } = {};
export const CHARSETS: { [key: string]: ICharset } = {};
/**
* The default character set, US.
*/
export const DEFAULT_CHARSET: Charset = CHARSETS['B'];
export const DEFAULT_CHARSET: ICharset = CHARSETS['B'];
/**
* DEC Special Character and Line Drawing Set.
+2 -2
View File
@@ -217,7 +217,7 @@ export class CompositionHelper {
if (!dontRecurse) {
setTimeout(() => this.updateCompositionElements(true), 0);
}
};
}
/**
* Clears the textarea's position so that the cursor does not blink on IE.
@@ -226,5 +226,5 @@ export class CompositionHelper {
private clearTextareaPosition(): void {
this.textarea.style.left = '';
this.textarea.style.top = '';
};
}
}
+1 -1
View File
@@ -76,4 +76,4 @@ export namespace C0 {
export const SP = '\x20';
/** Delete (Caret = ^?) */
export const DEL = '\x7f';
};
}
+7 -9
View File
@@ -26,7 +26,7 @@ export class InputHandler implements IInputHandler {
if (char >= ' ') {
// calculate print space
// expensive call, therefore we save width in line buffer
const ch_width = wcwidth(code);
const chWidth = wcwidth(code);
if (this._terminal.charset && this._terminal.charset[char]) {
char = this._terminal.charset[char];
@@ -36,7 +36,7 @@ export class InputHandler implements IInputHandler {
// insert combining char in last cell
// FIXME: needs handling after cursor jumps
if (!ch_width && this._terminal.buffer.x) {
if (!chWidth && this._terminal.buffer.x) {
// dont overflow left
if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) {
if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) {
@@ -56,7 +56,7 @@ export class InputHandler implements IInputHandler {
// goto next line if ch would overflow
// TODO: needs a global min terminal width of 2
if (this._terminal.buffer.x + ch_width - 1 >= this._terminal.cols) {
if (this._terminal.buffer.x + chWidth - 1 >= this._terminal.cols) {
// autowrap - DECAWM
if (this._terminal.wraparoundMode) {
this._terminal.buffer.x = 0;
@@ -70,7 +70,7 @@ export class InputHandler implements IInputHandler {
(<any>this._terminal.buffer.lines.get(this._terminal.buffer.y)).isWrapped = true;
}
} else {
if (ch_width === 2) // FIXME: check for xterm behavior
if (chWidth === 2) // FIXME: check for xterm behavior
return;
}
}
@@ -79,7 +79,7 @@ export class InputHandler implements IInputHandler {
// insert mode: move characters to right
if (this._terminal.insertMode) {
// do this twice for a fullwidth char
for (let moves = 0; moves < ch_width; ++moves) {
for (let moves = 0; moves < chWidth; ++moves) {
// remove last cell, if it's width is 0
// we have to adjust the second last cell as well
const removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop();
@@ -94,12 +94,12 @@ export class InputHandler implements IInputHandler {
}
}
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width, char.charCodeAt(0)];
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, chWidth, char.charCodeAt(0)];
this._terminal.buffer.x++;
this._terminal.updateRange(this._terminal.buffer.y);
// fullwidth char - set next cell width to zero and advance cursor
if (ch_width === 2) {
if (chWidth === 2) {
this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0, undefined];
this._terminal.buffer.x++;
}
@@ -928,7 +928,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 +1099,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();
+32 -8
View File
@@ -3,10 +3,11 @@
* @license MIT
*/
import { ILinkMatcherOptions } from './Interfaces';
import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset, LineData } from './Types';
import { ICharset, ILinkMatcherOptions } from './Interfaces';
import { LinkMatcherHandler, LinkMatcherValidationCallback, LineData } from './Types';
import { IColorSet, IRenderer } from './renderer/Interfaces';
import { IMouseZoneManager } from './input/Interfaces';
import { FontWeight } from './shared/Types';
export interface IBrowser {
isNode: boolean;
@@ -74,10 +75,10 @@ export interface IInputHandlingTerminal extends IEventEmitter {
options: ITerminalOptions;
cols: number;
rows: number;
charset: Charset;
charset: ICharset;
gcharset: number;
glevel: number;
charsets: Charset[];
charsets: ICharset[];
applicationKeypad: boolean;
applicationCursor: boolean;
originMode: boolean;
@@ -116,7 +117,7 @@ export interface IInputHandlingTerminal extends IEventEmitter {
blankLine(cur?: boolean, isWrapped?: boolean): LineData;
is(term: string): boolean;
send(data: string): void;
setgCharset(g: number, charset: Charset): void;
setgCharset(g: number, charset: ICharset): void;
resize(x: number, y: number): void;
log(text: string, data?: any): void;
reset(): void;
@@ -140,9 +141,12 @@ export interface ITerminalOptions {
enableBold?: boolean;
fontSize?: number;
fontFamily?: string;
fontWeight?: FontWeight;
fontWeightBold?: FontWeight;
handler?: (data: string) => void;
letterSpacing?: number;
lineHeight?: number;
macOptionIsMeta?: boolean;
rows?: number;
screenKeys?: boolean;
scrollback?: number;
@@ -169,7 +173,7 @@ export interface IBuffer {
prevStop(x?: number): number;
}
export interface IBufferSet {
export interface IBufferSet extends IEventEmitter {
alt: IBuffer;
normal: IBuffer;
active: IBuffer;
@@ -198,7 +202,6 @@ export interface ISelectionManager {
disable(): void;
enable(): void;
setBuffer(buffer: IBuffer): void;
setSelection(row: number, col: number, length: number): void;
}
@@ -248,7 +251,7 @@ export interface IEventEmitter {
export interface IListenerType {
(data?: any): void;
listener?: (data?: any) => void;
};
}
export interface ILinkMatcherOptions {
/**
@@ -352,3 +355,24 @@ export interface ITheme {
brightCyan?: string;
brightWhite?: string;
}
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;
}
+3 -3
View File
@@ -4,9 +4,9 @@
*/
import { assert } from 'chai';
import { ITerminal, ILinkifier, IBuffer, IBufferAccessor, IElementAccessor } from './Interfaces';
import { ITerminal, ILinkifier, ILinkMatcher, IBuffer, IBufferAccessor, IElementAccessor } from './Interfaces';
import { Linkifier } from './Linkifier';
import { LinkMatcher, LineData } from './Types';
import { LineData } from './Types';
import { IMouseZoneManager, IMouseZone } from './input/Interfaces';
import { MockBuffer } from './utils/TestUtils.test';
import { CircularList } from './utils/CircularList';
@@ -17,7 +17,7 @@ class TestLinkifier extends Linkifier {
Linkifier.TIME_BEFORE_LINKIFY = 0;
}
public get linkMatchers(): LinkMatcher[] { return this._linkMatchers; }
public get linkMatchers(): ILinkMatcher[] { return this._linkMatchers; }
public linkifyRows(): void { super.linkifyRows(0, this._terminal.buffer.lines.length - 1); }
}
+13 -13
View File
@@ -3,8 +3,8 @@
* @license MIT
*/
import { ILinkMatcherOptions, ITerminal, IBufferAccessor, ILinkifier, IElementAccessor } from './Interfaces';
import { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback, LineData, LinkHoverEvent, LinkHoverEventTypes } from './Types';
import { ILinkHoverEvent, ILinkMatcher, ILinkMatcherOptions, ITerminal, IBufferAccessor, ILinkifier, IElementAccessor } from './Interfaces';
import { LinkMatcherHandler, LinkMatcherValidationCallback, LineData, LinkHoverEventTypes } from './Types';
import { IMouseZoneManager } from './input/Interfaces';
import { MouseZone } from './input/MouseZoneManager';
import { EventEmitter } from './EventEmitter';
@@ -44,7 +44,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
*/
protected static TIME_BEFORE_LINKIFY = 200;
protected _linkMatchers: LinkMatcher[] = [];
protected _linkMatchers: ILinkMatcher[] = [];
private _mouseZoneManager: IMouseZoneManager;
private _rowsTimeoutId: number;
@@ -82,12 +82,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
@@ -143,7 +143,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) {
throw new Error('handler must be defined');
}
const matcher: LinkMatcher = {
const matcher: ILinkMatcher = {
id: this._nextLinkMatcherId++,
regex,
handler,
@@ -163,7 +163,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
* considered after older link matchers.
* @param matcher The link matcher to be added.
*/
private _addLinkMatcherToList(matcher: LinkMatcher): void {
private _addLinkMatcherToList(matcher: ILinkMatcher): void {
if (this._linkMatchers.length === 0) {
this._linkMatchers.push(matcher);
return;
@@ -219,7 +219,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
* @param offset The how much of the row has already been linkified.
* @return The link element(s) that were added.
*/
private _doLinkifyRow(rowIndex: number, text: string, matcher: LinkMatcher, offset: number = 0): void {
private _doLinkifyRow(rowIndex: number, text: string, matcher: ILinkMatcher, offset: number = 0): void {
// Iterate over nodes as we want to consider text nodes
let result = [];
const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID;
@@ -264,7 +264,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
* @param uri The URI of the link.
* @param matcher The link matcher for the link.
*/
private _addLink(x: number, y: number, uri: string, matcher: LinkMatcher): void {
private _addLink(x: number, y: number, uri: string, matcher: ILinkMatcher): void {
this._mouseZoneManager.add(new MouseZone(
x + 1,
x + 1 + uri.length,
@@ -276,17 +276,17 @@ export class Linkifier extends EventEmitter implements ILinkifier {
window.open(uri, '_blank');
},
e => {
this.emit(LinkHoverEventTypes.HOVER, <LinkHoverEvent>{ x, y, length: uri.length});
this.emit(LinkHoverEventTypes.HOVER, <ILinkHoverEvent>{ x, y, length: uri.length});
this._terminal.element.style.cursor = 'pointer';
},
e => {
this.emit(LinkHoverEventTypes.TOOLTIP, <LinkHoverEvent>{ x, y, length: uri.length});
this.emit(LinkHoverEventTypes.TOOLTIP, <ILinkHoverEvent>{ x, y, length: uri.length});
if (matcher.hoverTooltipCallback) {
matcher.hoverTooltipCallback(e, uri);
}
},
() => {
this.emit(LinkHoverEventTypes.LEAVE, <LinkHoverEvent>{ x, y, length: uri.length});
this.emit(LinkHoverEventTypes.LEAVE, <ILinkHoverEvent>{ x, y, length: uri.length});
this._terminal.element.style.cursor = '';
if (matcher.hoverLeaveCallback) {
matcher.hoverLeaveCallback();
+2 -3
View File
@@ -21,10 +21,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 +58,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 {
+23 -17
View File
@@ -8,7 +8,7 @@ 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 { ITerminal, ICircularList, ISelectionManager, IBuffer, IListenerType } from './Interfaces';
import { SelectionModel } from './SelectionModel';
import { LineData, CharData } from './Types';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';
@@ -95,10 +95,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
private _mouseMoveListener: EventListener;
private _mouseUpListener: EventListener;
private _trimListener: IListenerType;
constructor(
private _terminal: ITerminal,
private _buffer: IBuffer,
private _charMeasure: CharMeasure
) {
super();
@@ -109,18 +109,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(<MouseEvent>event);
this._mouseUpListener = event => this._onMouseUp(<MouseEvent>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 +145,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; }
@@ -547,6 +543,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.
+1 -1
View File
@@ -6,7 +6,7 @@
import { assert } from 'chai';
import { ITerminal } from './Interfaces';
import { SelectionModel } from './SelectionModel';
import {BufferSet} from './BufferSet';
import { BufferSet } from './BufferSet';
import { MockTerminal } from './utils/TestUtils.test';
class TestSelectionModel extends SelectionModel {
+18 -18
View File
@@ -15,17 +15,17 @@ import { assert } from 'chai';
import { Terminal } from './Terminal';
import { CHAR_DATA_CHAR_INDEX } from './Buffer';
let primitive_pty: any;
let primitivePty: any;
// fake sychronous pty write - read
// we just pipe the data from slave to master as a child program would do
// pty.js opens pipe fds with O_NONBLOCK
// just wait 10ms instead of setting fds to blocking mode
function ptyWriteRead(data: string, cb: (result: string) => void): void {
fs.writeSync(primitive_pty.slave, data);
fs.writeSync(primitivePty.slave, data);
setTimeout(() => {
let b = new Buffer(64000);
let bytes = fs.readSync(primitive_pty.master, b, 0, 64000, null);
let bytes = fs.readSync(primitivePty.master, b, 0, 64000, null);
cb(b.toString('utf8', 0, bytes));
});
}
@@ -37,7 +37,7 @@ function ptyReset(cb: (result: string) => void): void {
/* debug helpers */
// generate colorful noisy output to compare xterm and emulator cell states
function formatError(in_: string, out_: string, expected: string): string {
function formatError(input: string, output: string, expected: string): string {
function addLineNumber(start: number, color: string): (s: string) => string {
let counter = start || 0;
return function(s: string): string {
@@ -47,9 +47,9 @@ function formatError(in_: string, out_: string, expected: string): string {
}
let line80 = '12345678901234567890123456789012345678901234567890123456789012345678901234567890';
let s = '';
s += '\n\x1b[34m' + JSON.stringify(in_);
s += '\n\x1b[34m' + JSON.stringify(input);
s += '\n\x1b[33m ' + line80 + '\n';
s += out_.split('\n').map(addLineNumber(0, '\x1b[31m')).join('\n');
s += output.split('\n').map(addLineNumber(0, '\x1b[31m')).join('\n');
s += '\n\x1b[33m ' + line80 + '\n';
s += expected.split('\n').map(addLineNumber(0, '\x1b[32m')).join('\n');
return s;
@@ -58,15 +58,15 @@ function formatError(in_: string, out_: string, expected: string): string {
// simple debug output of terminal cells
function terminalToString(term: Terminal): string {
let result = '';
let line_s = '';
let lineText = '';
for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) {
line_s = '';
lineText = '';
for (let cell = 0; cell < term.cols; ++cell) {
line_s += term.buffer.lines.get(line)[cell][CHAR_DATA_CHAR_INDEX];
lineText += term.buffer.lines.get(line)[cell][CHAR_DATA_CHAR_INDEX];
}
// rtrim empty cells as xterm does
line_s = line_s.replace(/\s+$/, '');
result += line_s;
lineText = lineText.replace(/\s+$/, '');
result += lineText;
result += '\n';
}
return result;
@@ -83,7 +83,7 @@ if (os.platform() !== 'win32') {
/** some helpers for pty interaction */
// we need a pty in between to get the termios decorations
// for the basic test cases a raw pty device is enough
primitive_pty = pty.native.open(COLS, ROWS);
primitivePty = pty.native.open(COLS, ROWS);
/** tests */
describe('xterm output comparison', () => {
@@ -118,24 +118,24 @@ if (os.platform() !== 'win32') {
((filename: string) => {
it(filename.split('/').slice(-1)[0], done => {
ptyReset(() => {
let in_file = fs.readFileSync(filename, 'utf8');
ptyWriteRead(in_file, from_pty => {
let inFile = fs.readFileSync(filename, 'utf8');
ptyWriteRead(inFile, fromPty => {
// uncomment this to get log from terminal
// console.log = function(){};
// Perform a synchronous .write(data)
xterm.writeBuffer.push(from_pty);
xterm.writeBuffer.push(fromPty);
xterm.innerWrite();
let from_emulator = terminalToString(xterm);
let fromEmulator = terminalToString(xterm);
console.log = CONSOLE_LOG;
let expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8');
// Some of the tests have whitespace on the right of lines, we trim all the linex
// from xterm.js so ignore this for now at least.
let expectedRightTrimmed = expected.split('\n').map(l => l.replace(/\s+$/, '')).join('\n');
if (from_emulator !== expectedRightTrimmed) {
if (fromEmulator !== expectedRightTrimmed) {
// uncomment to get noisy output
throw new Error(formatError(in_file, from_emulator, expected));
throw new Error(formatError(inFile, fromEmulator, expected));
// throw new Error('mismatch');
}
done();
+32
View File
@@ -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;
+60 -41
View File
@@ -38,9 +38,9 @@ import { CharMeasure } from './utils/CharMeasure';
import * as Browser from './shared/utils/Browser';
import { MouseHelper } from './utils/MouseHelper';
import { CHARSETS } from './Charsets';
import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types';
import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper, ITheme, ILinkifier } from './Interfaces';
import { BellSound } from './utils/Sounds';
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';
@@ -69,16 +69,19 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
termName: 'xterm',
cursorBlink: false,
cursorStyle: 'block',
bellSound: BellSound,
bellSound: BELL_SOUND,
bellStyle: 'none',
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,
@@ -130,10 +133,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// charset
// The current charset
public charset: Charset;
public charset: ICharset;
public gcharset: number;
public glevel: number;
public charsets: Charset[];
public charsets: ICharset[];
// mouse properties
private decLocator: boolean; // This is unstable and never set
@@ -180,7 +183,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
private writeStopped: boolean;
// leftover surrogate high from previous write invocation
private surrogate_high: string;
private surrogateHigh: string;
// Store if user went browsing history in scrollback
private userScrolling: boolean;
@@ -191,7 +194,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;
@@ -279,7 +281,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.xoffSentToCatchUp = false;
this.writeStopped = false;
this.surrogate_high = '';
this.surrogateHigh = '';
this.userScrolling = false;
this.inputHandler = new InputHandler(this);
@@ -292,17 +294,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.
*/
@@ -360,6 +364,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}`);
@@ -413,11 +427,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();
@@ -442,7 +459,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.element.classList.add('focus');
this.showCursor();
this.emit('focus');
};
}
/**
* Blur the terminal, calling the blur function on the terminal's underlying
@@ -637,7 +654,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 => {
@@ -1370,7 +1387,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;
}
@@ -1391,6 +1408,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.
@@ -1570,6 +1600,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// page up
if (ev.shiftKey) {
result.scrollLines = -(this.rows - 1);
} else if (modifiers) {
result.key = C0.ESC + '[5;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[5~';
}
@@ -1578,6 +1610,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// page down
if (ev.shiftKey) {
result.scrollLines = this.rows - 1;
} else if (modifiers) {
result.key = C0.ESC + '[6;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[6~';
}
@@ -1691,8 +1725,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 <Esc> 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 <Esc> instead.
if (ev.keyCode >= 65 && ev.keyCode <= 90) {
result.key = C0.ESC + String.fromCharCode(ev.keyCode + 32);
} else if (ev.keyCode === 192) {
@@ -1725,7 +1759,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* @param g
* @param charset
*/
public setgCharset(g: number, charset: Charset): void {
public setgCharset(g: number, charset: ICharset): void {
this.charsets[g] = charset;
if (this.glevel === g) {
this.charset = charset;
@@ -1758,7 +1792,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;
}
@@ -2072,11 +2106,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();
}
@@ -2152,19 +2184,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
@@ -2217,7 +2236,7 @@ function matchColorDistance(r1: number, g1: number, b1: number, r2: number, g2:
return Math.pow(30 * (r1 - r2), 2)
+ Math.pow(59 * (g1 - g2), 2)
+ Math.pow(11 * (b1 - b2), 2);
};
}
function matchColor_(r1: number, g1: number, b1: number): number {
+1 -18
View File
@@ -3,33 +3,16 @@
* @license MIT
*/
export type LinkMatcher = {
id: number,
regex: RegExp,
handler: LinkMatcherHandler,
hoverTooltipCallback?: LinkMatcherHandler,
hoverLeaveCallback?: () => void,
matchIndex?: number,
validationCallback?: LinkMatcherValidationCallback,
priority?: number
};
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void;
export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
export type Charset = {[key: string]: string};
export type CharData = [number, string, number, number];
export type LineData = CharData[];
export type LinkHoverEvent = {
x: number,
y: number,
length: number
};
export enum LinkHoverEventTypes {
HOVER = 'linkhover',
TOOLTIP = 'linktooltip',
LEAVE = 'linkleave'
};
}
+3 -3
View File
@@ -119,7 +119,7 @@ export class Viewport implements IViewport {
this.viewportElement.scrollTop += ev.deltaY * multiplier;
// Prevent the page from scrolling when the terminal scrolls
ev.preventDefault();
};
}
/**
* Handles the touchstart event, recording the touch occurred.
@@ -127,7 +127,7 @@ export class Viewport implements IViewport {
*/
public onTouchStart(ev: TouchEvent): void {
this.lastTouchY = ev.touches[0].pageY;
};
}
/**
* Handles the touchmove event, scrolling the viewport if the position shifted.
@@ -141,5 +141,5 @@ export class Viewport implements IViewport {
}
this.viewportElement.scrollTop += deltaY;
ev.preventDefault();
};
}
}
+18
View File
@@ -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;
}
+2 -2
View File
@@ -5,14 +5,14 @@
import { assert, expect } from 'chai';
import * as attach from './attach'
import * as attach from './attach';
class MockTerminal {}
describe('attach addon', () => {
describe('apply', () => {
it('should do register the `attach` and `detach` methods', () => {
attach.apply(MockTerminal);
attach.apply(<any>MockTerminal);
assert.equal(typeof (<any>MockTerminal).prototype.attach, 'function');
assert.equal(typeof (<any>MockTerminal).prototype.detach, 'function');
});
+55 -55
View File
@@ -5,118 +5,118 @@
* Implements the attach method, that attaches the terminal to a WebSocket stream.
*/
/// <reference path="../../../typings/xterm.d.ts"/>
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, socket, bidirectional, buffered) {
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
term.socket = socket;
export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
const addonTerminal = <IAttachAddonTerminal>term;
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
addonTerminal.__socket = socket;
term._flushBuffer = function() {
term.write(term._attachSocketBuffer);
term._attachSocketBuffer = null;
addonTerminal.__flushBuffer = () => {
addonTerminal.write(addonTerminal.__attachSocketBuffer);
addonTerminal.__attachSocketBuffer = null;
};
term._pushToBuffer = function(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);
}
};
var myTextDecoder;
let myTextDecoder;
term._getMessage = function(ev) {
var str;
if (typeof ev.data === "object") {
addonTerminal.__getMessage = function(ev: MessageEvent): void {
let str;
if (typeof ev.data === 'object') {
if (ev.data instanceof ArrayBuffer) {
if (!myTextDecoder) {
myTextDecoder = new TextDecoder();
}
str = myTextDecoder.decode( ev.data );
}
else {
throw "TODO: handle Blob?";
} else {
throw 'TODO: handle Blob?';
}
}
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 = function(data) {
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, socket) {
term.off('data', term._sendData);
export function detach(term: Terminal, socket: WebSocket): void {
const addonTerminal = <IAttachAddonTerminal>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) {
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, bidirectional, buffered) {
return attach(this, socket, bidirectional, buffered);
(<any>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) {
return detach(this, socket);
(<any>terminalConstructor.prototype).detach = function (socket: WebSocket): void {
detach(this, socket);
};
}
+2 -1
View File
@@ -5,6 +5,7 @@
"rootDir": ".",
"outDir": "../../../lib/addons/attach/",
"sourceMap": true,
"removeComments": true
"removeComments": true,
"declaration": true
}
}
+2 -2
View File
@@ -5,14 +5,14 @@
import { assert, expect } from 'chai';
import * as fit from './fit'
import * as fit from './fit';
class MockTerminal {}
describe('fit addon', () => {
describe('apply', () => {
it('should do register the `proposeGeometry` and `fit` methods', () => {
fit.apply(MockTerminal);
fit.apply(<any>MockTerminal);
assert.equal(typeof (<any>MockTerminal).prototype.proposeGeometry, 'function');
assert.equal(typeof (<any>MockTerminal).prototype.fit, 'function');
});
+35 -26
View File
@@ -13,43 +13,52 @@
* row and truncate its width with the current number of columns).
*/
export function proposeGeometry(term) {
/// <reference path="../../../typings/xterm.d.ts"/>
import { Terminal } from 'xterm';
export interface IGeometry {
rows: number;
cols: number;
}
export function proposeGeometry(term: Terminal): IGeometry {
if (!term.element.parentElement) {
return null;
}
var parentElementStyle = window.getComputedStyle(term.element.parentElement);
var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17);
var elementStyle = window.getComputedStyle(term.element);
var elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom'));
var elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left'));
var availableHeight = parentElementHeight - elementPaddingVer;
var availableWidth = parentElementWidth - elementPaddingHor;
var geometry = {
cols: Math.floor(availableWidth / term.renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / term.renderer.dimensions.actualCellHeight)
const parentElementStyle = window.getComputedStyle(term.element.parentElement);
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17);
const elementStyle = window.getComputedStyle(term.element);
const elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom'));
const elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left'));
const availableHeight = parentElementHeight - elementPaddingVer;
const availableWidth = parentElementWidth - elementPaddingHor;
const geometry = {
cols: Math.floor(availableWidth / (<any>term).renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / (<any>term).renderer.dimensions.actualCellHeight)
};
return geometry;
};
}
export function fit(term) {
var geometry = proposeGeometry(term);
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();
(<any>term).renderer.clear();
term.resize(geometry.cols, geometry.rows);
}
}
};
export function apply(terminalConstructor) {
terminalConstructor.prototype.proposeGeometry = function() {
return proposeGeometry(this);
}
terminalConstructor.prototype.fit = function() {
return fit(this);
}
}
export function apply(terminalConstructor: typeof Terminal): void {
(<any>terminalConstructor.prototype).proposeGeometry = function (): IGeometry {
return proposeGeometry(this);
};
(<any>terminalConstructor.prototype).fit = function (): void {
fit(this);
};
}
+2 -1
View File
@@ -5,6 +5,7 @@
"rootDir": ".",
"outDir": "../../../lib/addons/fit/",
"sourceMap": true,
"removeComments": true
"removeComments": true,
"declaration": true
}
}
+2 -2
View File
@@ -5,14 +5,14 @@
import { assert, expect } from 'chai';
import * as fullscreen from './fullscreen'
import * as fullscreen from './fullscreen';
class MockTerminal {}
describe('fullscreen addon', () => {
describe('apply', () => {
it('should do register the `toggleFullscreen` method', () => {
fullscreen.apply(MockTerminal);
fullscreen.apply(<any>MockTerminal);
assert.equal(typeof (<any>MockTerminal).prototype.toggleFullScreen, 'function');
});
});
+13 -9
View File
@@ -3,15 +3,19 @@
* @license MIT
*/
/// <reference path="../../../typings/xterm.d.ts"/>
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, fullscreen) {
var fn;
export function toggleFullScreen(term: Terminal, fullscreen: boolean): void {
let fn: string;
if (typeof fullscreen == 'undefined') {
if (typeof fullscreen === 'undefined') {
fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';
} else if (!fullscreen) {
fn = 'remove';
@@ -20,10 +24,10 @@ export function toggleFullScreen(term, fullscreen) {
}
term.element.classList[fn]('fullscreen');
};
}
export function apply(terminalConstructor) {
terminalConstructor.prototype.toggleFullScreen = function (fullscreen) {
return toggleFullScreen(this, fullscreen);
export function apply(terminalConstructor: typeof Terminal): void {
(<any>terminalConstructor.prototype).toggleFullScreen = function (fullscreen: boolean): void {
toggleFullScreen(this, fullscreen);
};
}
+2 -1
View File
@@ -5,6 +5,7 @@
"rootDir": ".",
"outDir": "../../../lib/addons/fullscreen/",
"sourceMap": true,
"removeComments": true
"removeComments": true,
"declaration": true
}
}
+19
View File
@@ -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;
}
+20 -6
View File
@@ -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;
}
}
+25 -20
View File
@@ -3,8 +3,11 @@
* @license MIT
*/
import { SearchHelper } from './SearchHelper';
/// <reference path="../../../typings/xterm.d.ts"/>
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,12 +15,13 @@ 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 = <ISearchAddonTerminal>terminal;
if (!addonTerminal.__searchHelper) {
addonTerminal.__searchHelper = new SearchHelper(addonTerminal);
}
return (<SearchHelper>terminal.searchHelper).findNext(term);
};
return addonTerminal.__searchHelper.findNext(term);
}
/**
* Find the previous instance of the term, then scroll to and select it. If it
@@ -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);
}
return (<SearchHelper>terminal.searchHelper).findPrevious(term);
};
export function apply(terminalConstructor) {
terminalConstructor.prototype.findNext = function(term) {
return findNext(this, term);
}
terminalConstructor.prototype.findPrevious = function(term) {
return findPrevious(this, term);
export function findPrevious(terminal: Terminal, term: string): boolean {
const addonTerminal = <ISearchAddonTerminal>terminal;
if (!addonTerminal.__searchHelper) {
addonTerminal.__searchHelper = new SearchHelper(addonTerminal);
}
return addonTerminal.__searchHelper.findPrevious(term);
}
export function apply(terminalConstructor: typeof Terminal): void {
(<any>terminalConstructor.prototype).findNext = function(term: string): boolean {
return findNext(this, term);
};
(<any>terminalConstructor.prototype).findPrevious = function(term: string): boolean {
return findPrevious(this, term);
};
}
+2 -1
View File
@@ -5,6 +5,7 @@
"rootDir": ".",
"outDir": "../../../lib/addons/search/",
"sourceMap": true,
"removeComments": true
"removeComments": true,
"declaration": true
}
}
+19
View File
@@ -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;
}
+2 -2
View File
@@ -5,14 +5,14 @@
import { assert, expect } from 'chai';
import * as terminado from './terminado'
import * as terminado from './terminado';
class MockTerminal {}
describe('terminado addon', () => {
describe('apply', () => {
it('should do register the `terminadoAttach` and `terminadoDetach` methods', () => {
terminado.apply(MockTerminal);
terminado.apply(<any>MockTerminal);
assert.equal(typeof (<any>MockTerminal).prototype.terminadoAttach, 'function');
assert.equal(typeof (<any>MockTerminal).prototype.terminadoDetach, 'function');
});
+52 -51
View File
@@ -6,106 +6,107 @@
* WebSocket stream.
*/
/// <reference path="../../../typings/xterm.d.ts"/>
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, socket, bidirectional, buffered) {
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
term.socket = socket;
export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {
const addonTerminal = <ITerminadoAddonTerminal>term;
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
addonTerminal.__socket = socket;
term._flushBuffer = function() {
term.write(term._attachSocketBuffer);
term._attachSocketBuffer = null;
addonTerminal.__flushBuffer = () => {
addonTerminal.write(addonTerminal.__attachSocketBuffer);
addonTerminal.__attachSocketBuffer = null;
};
term._pushToBuffer = function(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 = function(ev) {
var data = JSON.parse(ev.data)
if( data[0] == "stdout" ) {
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 = function(data) {
addonTerminal.__sendData = (data: string) => {
socket.send(JSON.stringify(['stdin', data]));
};
term._setSize = function(size) {
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, socket) {
term.off('data', term._sendData);
export function terminadoDetach(term: Terminal, socket: WebSocket): void {
const addonTerminal = <ITerminadoAddonTerminal>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) {
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, bidirectional, buffered) {
(<any>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) {
(<any>terminalConstructor.prototype).terminadoDetach = function (socket: WebSocket): void {
return terminadoDetach(this, socket);
};
}
+2 -1
View File
@@ -5,6 +5,7 @@
"rootDir": ".",
"outDir": "../../../lib/addons/terminado/",
"sourceMap": true,
"removeComments": true
"removeComments": true,
"declaration": true
}
}
+10
View File
@@ -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;
}
+2 -1
View File
@@ -5,6 +5,7 @@
"rootDir": ".",
"outDir": "../../../lib/addons/winptyCompat/",
"sourceMap": true,
"removeComments": true
"removeComments": true,
"declaration": true
}
}
+2 -2
View File
@@ -5,14 +5,14 @@
import { assert, expect } from 'chai';
import * as winptyCompat from './winptyCompat'
import * as winptyCompat from './winptyCompat';
class MockTerminal {}
describe('winptyCompat addon', () => {
describe('apply', () => {
it('should do register the `winptyCompatInit` method', () => {
winptyCompat.apply(MockTerminal);
winptyCompat.apply(<any>MockTerminal);
assert.equal(typeof (<any>MockTerminal).prototype.winptyCompatInit, 'function');
});
});
+34 -27
View File
@@ -3,36 +3,43 @@
* @license MIT
*/
export function winptyCompatInit(terminal): void {
// Don't do anything when the platform is not Windows
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
if (!isWindows) {
return;
/// <reference path="../../../typings/xterm.d.ts"/>
import { Terminal } from 'xterm';
import { IWinptyCompatAddonTerminal } from './Interfaces';
export function winptyCompatInit(terminal: Terminal): void {
const addonTerminal = <IWinptyCompatAddonTerminal>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);
(<any>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);
(<any>nextLine).isWrapped = true;
}
});
});
}
export function apply(terminalConstructor) {
terminalConstructor.prototype.winptyCompatInit = function(): void {
export function apply(terminalConstructor: typeof Terminal): void {
(<any>terminalConstructor.prototype).winptyCompatInit = function (): void {
winptyCompatInit(this);
};
}
+2 -1
View File
@@ -5,6 +5,7 @@
"rootDir": ".",
"outDir": "../../../lib/addons/zmodem/",
"sourceMap": true,
"removeComments": true
"removeComments": true,
"declaration": true
}
}
+2 -2
View File
@@ -5,14 +5,14 @@
import { assert, expect } from 'chai';
import * as zmodem from './zmodem'
import * as zmodem from './zmodem';
class MockTerminal {}
describe('zmodem addon', () => {
describe('apply', () => {
it('should do register the `zmodemAttach` method and `zmodemBrowser` attribute', () => {
zmodem.apply(MockTerminal);
zmodem.apply(<any>MockTerminal);
assert.equal(typeof (<any>MockTerminal).prototype.zmodemAttach, 'function');
assert.equal(typeof (<any>MockTerminal).prototype.zmodemBrowser, 'object');
});
+31 -29
View File
@@ -1,3 +1,12 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
/// <reference path="../../../typings/xterm.d.ts"/>
import { Terminal } from 'xterm';
/**
*
* Allow xterm.js to handle ZMODEM uploads and downloads.
@@ -27,47 +36,40 @@
* via `detach()` and a re-`attach()`.)
*/
let Zmodem;
let zmodem;
export function zmodemAttach(term, ws, opts) {
if (!opts) opts = {};
export interface IZModemOptions {
noTerminalWriteOutsideSession?: boolean;
}
var senderFunc = function _ws_sender_func(octets) {
ws.send(new Uint8Array(octets));
};
export function zmodemAttach(term: Terminal, ws: WebSocket, opts: IZModemOptions = {}): void {
const senderFunc = (octets: ArrayLike<number>) => ws.send(new Uint8Array(octets));
var zsentry;
let zsentry;
function _shouldWrite() {
function _shouldWrite(): boolean {
return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;
}
zsentry = new Zmodem.Sentry( {
to_terminal: function _to_terminal(octets) {
zsentry = new zmodem.Sentry({
to_terminal: (octets: ArrayLike<number>) => {
if (_shouldWrite()) {
term.write(
String.fromCharCode.apply(String, octets)
);
}
},
sender: senderFunc,
on_retract: () => (<any>term).emit('zmodemRetract'),
on_detect: (detection: any) => (<any>term).emit('zmodemDetect', detection)
});
on_retract: function _on_retract() {
term.emit('zmodemRetract');
},
function handleWSMessage(evt: MessageEvent): void {
on_detect: function _on_detect(detection) {
term.emit('zmodemDetect', detection);
},
} );
function handleWSMessage(evt) {
//In testing with xterm.js’s demo the first message was
//always text even if the rest were binary. While that
//may be specific to xterm.js’s demo, ultimately we
//should reject anything that isn’t binary.
// In testing with xterm.js’s demo the first message was
// always text even if the rest were binary. While that
// may be specific to xterm.js’s demo, ultimately we
// should reject anything that isn’t binary.
if (typeof evt.data === 'string') {
if (_shouldWrite()) {
term.write(evt.data);
@@ -82,9 +84,9 @@ export function zmodemAttach(term, ws, opts) {
ws.addEventListener('message', handleWSMessage);
}
export function apply(terminalConstructor) {
Zmodem = (typeof window == 'object') ? (<any>window).ZModem : {Browser: null}; // Nullify browser for tests
export function apply(terminalConstructor: typeof Terminal): void {
zmodem = (typeof window === 'object') ? (<any>window).ZModem : {Browser: null}; // Nullify browser for tests
terminalConstructor.prototype.zmodemAttach = zmodemAttach.bind(this, this);
terminalConstructor.prototype.zmodemBrowser = Zmodem.Browser
(<any>terminalConstructor.prototype).zmodemAttach = zmodemAttach.bind(this, this);
(<any>terminalConstructor.prototype).zmodemBrowser = zmodem.Browser;
}
+1 -1
View File
@@ -178,7 +178,7 @@ export class MouseZoneManager implements IMouseZoneManager {
if (zone.y === coords[1] && zone.x1 <= coords[0] && zone.x2 > coords[0]) {
return zone;
}
};
}
return null;
}
}
+14 -6
View File
@@ -201,7 +201,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 +269,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 +289,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 +329,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}`;
}
}
+8
View File
@@ -13,6 +13,8 @@ export const CHAR_ATLAS_CELL_SPACING = 1;
interface ICharAtlasConfig {
fontSize: number;
fontFamily: string;
fontWeight: string;
fontWeightBold: string;
scaledCharWidth: number;
scaledCharHeight: number;
colors: IColorSet;
@@ -76,6 +78,8 @@ export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledC
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,
@@ -105,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
};
}
@@ -117,6 +123,8 @@ 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 &&
+4 -3
View File
@@ -11,7 +11,7 @@ import { FLAGS } from './Types';
import { BaseRenderLayer } from './BaseRenderLayer';
import { CharData } from '../Types';
interface CursorState {
interface ICursorState {
x: number;
y: number;
isFocused: boolean;
@@ -25,7 +25,7 @@ interface CursorState {
const BLINK_INTERVAL = 600;
export class CursorRenderLayer extends BaseRenderLayer {
private _state: CursorState;
private _state: ICursorState;
private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void};
private _cursorBlinkStateManager: CursorBlinkStateManager;
private _isFocused: boolean;
@@ -108,9 +108,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {
// Only render if the animation frame is not active
if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) {
this._render(terminal, false);
} else {
this._cursorBlinkStateManager.restartBlinkAnimation(terminal);
}
}
+7 -7
View File
@@ -4,20 +4,20 @@
*/
import { IColorSet, IRenderDimensions } from './Interfaces';
import { IBuffer, ICharMeasure, ITerminal, ILinkifierAccessor } from '../Interfaces';
import { IBuffer, ICharMeasure, ILinkHoverEvent, ITerminal, ILinkifierAccessor } from '../Interfaces';
import { CHAR_DATA_ATTR_INDEX } from '../Buffer';
import { GridCache } from './GridCache';
import { FLAGS } from './Types';
import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer';
import { LinkHoverEvent, LinkHoverEventTypes } from '../Types';
import { LinkHoverEventTypes } from '../Types';
export class LinkRenderLayer extends BaseRenderLayer {
private _state: LinkHoverEvent = null;
private _state: ILinkHoverEvent = null;
constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) {
super(container, 'link', zIndex, true, colors);
terminal.linkifier.on(LinkHoverEventTypes.HOVER, (e: LinkHoverEvent) => this._onLinkHover(e));
terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: LinkHoverEvent) => this._onLinkLeave(e));
terminal.linkifier.on(LinkHoverEventTypes.HOVER, (e: ILinkHoverEvent) => this._onLinkHover(e));
terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: ILinkHoverEvent) => this._onLinkLeave(e));
}
public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
@@ -37,13 +37,13 @@ export class LinkRenderLayer extends BaseRenderLayer {
}
}
private _onLinkHover(e: LinkHoverEvent): void {
private _onLinkHover(e: ILinkHoverEvent): void {
this._ctx.fillStyle = this._colors.foreground;
this.fillBottomLineAtCells(e.x, e.y, e.length);
this._state = e;
}
private _onLinkLeave(e: LinkHoverEvent): void {
private _onLinkLeave(e: ILinkHoverEvent): void {
this._clearCurrentLink();
}
}
+1
View File
@@ -57,6 +57,7 @@ export class Renderer extends EventEmitter implements IRenderer {
};
this._devicePixelRatio = window.devicePixelRatio;
this._updateDimensions();
this.onOptionsChanged();
this._screenDprMonitor = new ScreenDprMonitor();
this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio));
+5 -5
View File
@@ -33,7 +33,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 +166,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;
@@ -192,9 +192,9 @@ export class TextRenderLayer extends BaseRenderLayer {
}
}
/**
* Whether a character is overlapping to the next cell.
*/
/**
* Whether a character is overlapping to the next cell.
*/
private _isOverlapping(charData: CharData): boolean {
// Only single cell characters can be overlapping, rendering issues can
// occur without this check
+1 -1
View File
@@ -13,4 +13,4 @@ export enum FLAGS {
INVERSE = 8,
INVISIBLE = 16,
DIM = 32
};
}
+11 -4
View File
@@ -4,6 +4,7 @@
*/
import { isFirefox } from './utils/Browser';
import { FontWeight } from './Types';
declare const Promise: any;
@@ -19,6 +20,8 @@ export interface ICharAtlasRequest {
scaledCharHeight: number;
fontSize: number;
fontFamily: string;
fontWeight: FontWeight;
fontWeightBold: FontWeight;
background: string;
foreground: string;
ansiColors: string[];
@@ -47,7 +50,7 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number
ctx.save();
ctx.fillStyle = request.foreground;
ctx.font = `${request.fontSize * request.devicePixelRatio}px ${request.fontFamily}`;
ctx.font = getFont(request.fontWeight, request);
ctx.textBaseline = 'top';
// Default color
@@ -61,7 +64,7 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number
}
// Default color bold
ctx.save();
ctx.font = `bold ${ctx.font}`;
ctx.font = getFont(request.fontWeightBold, request);
for (let i = 0; i < 256; i++) {
ctx.save();
ctx.beginPath();
@@ -73,11 +76,11 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number
ctx.restore();
// Colors 0-15
ctx.font = `${request.fontSize * request.devicePixelRatio}px ${request.fontFamily}`;
ctx.font = getFont(request.fontWeight, request);
for (let colorIndex = 0; colorIndex < 16; colorIndex++) {
// colors 8-15 are bold
if (colorIndex === 8) {
ctx.font = `bold ${ctx.font}`;
ctx.font = getFont(request.fontWeightBold, request);
}
const y = (colorIndex + 2) * cellHeight;
// Draw ascii characters
@@ -131,3 +134,7 @@ function clearColor(imageData: ImageData, r: number, g: number, b: number): void
}
}
}
function getFont(fontWeight: FontWeight, request: ICharAtlasRequest): string {
return `${fontWeight} ${request.fontSize * request.devicePixelRatio}px ${request.fontFamily}`;
}
+6
View File
@@ -0,0 +1,6 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
+1 -1
View File
@@ -7,4 +7,4 @@
// This sound is released under the Creative Commons Attribution 3.0 Unported
// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been
// made, apart from the conversion to base64.
export const BellSound = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';
export const BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';
+1 -1
View File
@@ -6,7 +6,7 @@
import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType, IInputHandlingTerminal, IViewport, ICircularList, ICompositionHelper, ITheme, ILinkifier, IMouseHelper } from '../Interfaces';
import { LineData } from '../Types';
import { Buffer } from '../Buffer';
import * as Browser from './Browser';
import * as Browser from '../shared/utils/Browser';
import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Interfaces';
export class MockTerminal implements ITerminal {
+2 -1
View File
@@ -5,7 +5,8 @@
"rootDir": "src",
"outDir": "lib",
"sourceMap": true,
"removeComments": true
"removeComments": true,
"declaration": true
},
"include": [
"src/**/*"
+18 -6
View File
@@ -1,5 +1,9 @@
{
"rules": {
"array-type": [
true,
"array"
],
"class-name": true,
"comment-format": [
true,
@@ -9,6 +13,11 @@
true,
"spaces"
],
"interface-name": [
true,
"always-prefix"
],
"interface-over-type-literal": true,
"typedef": [
true,
"call-signature",
@@ -18,10 +27,7 @@
"no-eval": true,
"no-internal-module": true,
"no-trailing-whitespace": true,
"one-variable-per-declaration": [
true,
true
],
"one-variable-per-declaration": true,
"no-unsafe-finally": true,
"no-var-keyword": true,
"quotemark": [
@@ -48,15 +54,21 @@
],
"variable-name": [
true,
"ban-keywords"
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-module",
"check-operator",
"check-rest-spread",
"check-separator",
"check-type"
"check-type",
"check-type-operator",
"check-preblock"
]
}
}
+29 -3
View File
@@ -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,11 @@ declare module 'xterm' {
*/
lineHeight?: number;
/**
* Whether to treat option as the meta key.
*/
macOptionIsMeta?: boolean;
/**
* The number of rows in the terminal.
*/
@@ -404,12 +424,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'): boolean;
getOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean;
/**
* Retrieves an option's value from the terminal.
* @param key The option key.
@@ -437,6 +457,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 +480,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', value: boolean): void;
setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void;
/**
* Sets an option on the terminal.
* @param key The option key.