Merge branch 'master' into window_manipulation

This commit is contained in:
jerch
2019-10-29 08:28:02 +01:00
committed by GitHub
18 changed files with 176 additions and 1283 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-fit",
"version": "0.2.1",
"version": "0.3.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-search",
"version": "0.2.1",
"version": "0.3.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
+2 -2
View File
@@ -17,7 +17,7 @@ declare module 'xterm-addon-search' {
/**
* Whether to search for a whole word, the result is only valid if it's
* suppounded in "non-word" characters such as `_`, `(`, `)` or space.
* surrounded in "non-word" characters such as `_`, `(`, `)` or space.
*/
wholeWord?: boolean;
@@ -27,7 +27,7 @@ declare module 'xterm-addon-search' {
caseSensitive?: boolean;
/**
* Whether to do an indcremental search, this will expand the selection if it
* Whether to do an incremental search, this will expand the selection if it
* still matches the term the user typed. Note that this only affects
* `findNext`, not `findPrevious`.
*/
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-webgl",
"version": "0.2.1",
"version": "0.3.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
+56
View File
@@ -0,0 +1,56 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*
* Script to initialize addon packages under "addons/" with outer deps.
*/
const path = require('path');
const cp = require('child_process');
const fs = require('fs');
const PACKAGE_ROOT = path.join(__dirname, '..');
// install addon deps
const addonsPath = path.join(PACKAGE_ROOT, 'addons');
if (fs.existsSync(addonsPath)) {
console.log('pulling addon dependencies...');
// whether to use yarn or npm
let hasYarn = false;
try {
cp.execSync('yarn --version').toString();
hasYarn = true;
} catch(e) {}
// walk all addon folders
fs.readdir(addonsPath, (err, files) => {
files.forEach(folder => {
const addonPath = path.join(addonsPath, folder);
// install only if there are dependencies listed
let packageJson;
try {
packageJson = require(path.join(addonPath, 'package.json'));
} catch (e) {
// swallow as changing branches can leave folders around
}
if (packageJson
&& (
(packageJson.devDependencies && Object.keys(packageJson.devDependencies).length)
|| (packageJson.dependencies && Object.keys(packageJson.dependencies).length)
)
)
{
console.log('Preparing', folder);
if (hasYarn) {
cp.execSync('yarn', {cwd: addonPath});
} else {
cp.execSync('npm install', {cwd: addonPath});
}
} else {
console.log('Skipped', folder);
}
});
});
}
+8 -8
View File
@@ -14,20 +14,20 @@ function startServer() {
logs = {};
app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css'));
app.get('/logo.png', (req, res) => {
res.sendFile(__dirname + '/logo.png'); // lgtm [js/missing-rate-limiting]
app.get('/logo.png', (req, res) => { // lgtm [js/missing-rate-limiting]
res.sendFile(__dirname + '/logo.png');
});
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html'); // lgtm [js/missing-rate-limiting]
app.get('/', (req, res) => { // lgtm [js/missing-rate-limiting]
res.sendFile(__dirname + '/index.html');
});
app.get('/test', (req, res) => {
res.sendFile(__dirname + '/test.html'); // lgtm [js/missing-rate-limiting]
app.get('/test', (req, res) => { // lgtm [js/missing-rate-limiting]
res.sendFile(__dirname + '/test.html');
});
app.get('/style.css', (req, res) => {
res.sendFile(__dirname + '/style.css'); // lgtm [js/missing-rate-limiting]
app.get('/style.css', (req, res) => { // lgtm [js/missing-rate-limiting]
res.sendFile(__dirname + '/style.css');
});
app.use('/dist', express.static(__dirname + '/dist'));
+5 -3
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "4.1.0",
"version": "4.2.0",
"main": "lib/xterm.js",
"style": "css/xterm.css",
"types": "typings/xterm.d.ts",
@@ -11,13 +11,15 @@
"prepackage": "npm run build",
"package": "webpack",
"start": "node demo/start",
"lint": "tslint 'src/**/*.ts' 'addons/**/*.ts'",
"lint": "tslint 'src/**/*.ts' 'addons/*/src/**/*.ts'",
"test": "npm run test-unit",
"posttest": "npm run lint",
"test-api": "mocha \"**/*.api.js\"",
"test-unit": "node ./bin/test.js",
"build": "tsc -b ./tsconfig.all.json",
"prepare": "npm run build",
"prepare": "npm run setup",
"setup": "npm run build",
"presetup": "node ./bin/install-addons.js",
"prepublishOnly": "npm run package",
"watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput",
"benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
+18
View File
@@ -1248,4 +1248,22 @@ describe('InputHandler', () => {
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '125', '125', '125', '125', '125']);
});
});
it('should parse big chunks in smaller subchunks', () => {
// max single chunk size is hardcoded as 131072
const calls: any[] = [];
const term = new TestTerminal({cols: 10, rows: 10});
(term as any)._inputHandler._parser.parse = (data: Uint32Array, length: number) => {
calls.push([data.length, length]);
};
term.writeSync('12345');
term.writeSync('a'.repeat(10000));
term.writeSync('a'.repeat(200000));
term.writeSync('a'.repeat(300000));
assert.deepEqual(calls, [
[4096, 5],
[10000, 10000],
[131072, 131072], [131072, 200000 - 131072],
[131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072]
]);
});
});
+32 -15
View File
@@ -28,6 +28,11 @@ import { DcsHandler } from 'common/parser/DcsParser';
*/
const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2};
/**
* Max length of the UTF32 input buffer. Real memory consumption is 4 times higher.
*/
const MAX_PARSEBUFFER_LENGTH = 131072;
/**
* DCS subparser implementations
@@ -336,14 +341,28 @@ export class InputHandler extends Disposable implements IInputHandler {
this._logService.debug('parsing data', data);
// resize input buffer if needed
if (this._parseBuffer.length < data.length) {
this._parseBuffer = new Uint32Array(data.length);
if (this._parseBuffer.length < MAX_PARSEBUFFER_LENGTH) {
this._parseBuffer = new Uint32Array(Math.min(data.length, MAX_PARSEBUFFER_LENGTH));
}
}
this._parser.parse(this._parseBuffer,
(typeof data === 'string')
// process big data in smaller chunks
if (data.length > MAX_PARSEBUFFER_LENGTH) {
for (let i = 0; i < data.length; i += MAX_PARSEBUFFER_LENGTH) {
const end = i + MAX_PARSEBUFFER_LENGTH < data.length ? i + MAX_PARSEBUFFER_LENGTH : data.length;
const len = (typeof data === 'string')
? this._stringDecoder.decode(data.substring(i, end), this._parseBuffer)
: this._utf8Decoder.decode(data.subarray(i, end), this._parseBuffer);
this._parser.parse(this._parseBuffer, len);
}
} else {
const len = (typeof data === 'string')
? this._stringDecoder.decode(data, this._parseBuffer)
: this._utf8Decoder.decode(data, this._parseBuffer)
);
: this._utf8Decoder.decode(data, this._parseBuffer);
this._parser.parse(this._parseBuffer, len);
}
buffer = this._bufferService.buffer;
if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {
@@ -1416,16 +1435,14 @@ export class InputHandler extends Disposable implements IInputHandler {
// focusout: ^[[O
this._terminal.sendFocus = true;
break;
case 1005: // utf8 ext mode mouse
// for wide terminals
// simply encodes large values as utf8 characters
this._coreMouseService.activeEncoding = 'UTF8';
case 1005: // utf8 ext mode mouse - removed in #2507
this._logService.debug('DECSET 1005 not supported (see #2507)');
break;
case 1006: // sgr ext mode mouse
this._coreMouseService.activeEncoding = 'SGR';
break;
case 1015: // urxvt ext mode mouse
this._coreMouseService.activeEncoding = 'URXVT';
case 1015: // urxvt ext mode mouse - removed in #2507
this._logService.debug('DECSET 1015 not supported (see #2507)');
break;
case 25: // show cursor
this._terminal.cursorHidden = false;
@@ -1589,14 +1606,14 @@ export class InputHandler extends Disposable implements IInputHandler {
case 1004: // send focusin/focusout events
this._terminal.sendFocus = false;
break;
case 1005: // utf8 ext mode mouse
this._coreMouseService.activeEncoding = 'DEFAULT';
case 1005: // utf8 ext mode mouse - removed in #2507
this._logService.debug('DECRST 1005 not supported (see #2507)');
break;
case 1006: // sgr ext mode mouse
this._coreMouseService.activeEncoding = 'DEFAULT';
break;
case 1015: // urxvt ext mode mouse
this._coreMouseService.activeEncoding = 'DEFAULT';
case 1015: // urxvt ext mode mouse - removed in #2507
this._logService.debug('DECRST 1015 not supported (see #2507)');
break;
case 25: // hide cursor
this._terminal.cursorHidden = true;
+1 -1
View File
@@ -308,7 +308,7 @@ export class Linkifier implements ILinkifier {
if (matcher.hoverTooltipCallback) {
// Note that IViewportRange use 1-based coordinates to align with escape sequences such
// as CUP which use 1,1 as the default for row/col
matcher.hoverTooltipCallback(e, uri, { start: { row: y1 + 1, col: x1 + 1 }, end: { row: y2 + 1, col: x2 } });
matcher.hoverTooltipCallback(e, uri, { start: { x: x1, y: y1 }, end: { x: x2, y: y2 } });
}
},
() => {
+5 -5
View File
@@ -44,13 +44,13 @@ export interface IViewport extends IDisposable {
}
export interface IViewportRange {
start: IViewportCellPosition;
end: IViewportCellPosition;
start: IViewportRangePosition;
end: IViewportRangePosition;
}
export interface IViewportCellPosition {
col: number;
row: number;
export interface IViewportRangePosition {
x: number;
y: number;
}
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void;
+1 -1
View File
@@ -237,7 +237,7 @@ export class Viewport extends Disposable implements IViewport {
if ((modifier === 'alt' && ev.altKey) ||
(modifier === 'ctrl' && ev.ctrlKey) ||
(modifier === 'shift' && ev.shiftKey)) {
return amount * this._optionsService.options.fastScrollSensitivity;
return amount * this._optionsService.options.fastScrollSensitivity * this._optionsService.options.scrollSensitivity;
}
return amount * this._optionsService.options.scrollSensitivity;
+15 -4
View File
@@ -16,11 +16,13 @@ export interface IEvent<T> {
export interface IEventEmitter<T> {
event: IEvent<T>;
fire(data: T): void;
dispose(): void;
}
export class EventEmitter<T> implements IEventEmitter<T> {
private _listeners: IListener<T>[] = [];
private _event?: IEvent<T>;
private _disposed: boolean = false;
public get event(): IEvent<T> {
if (!this._event) {
@@ -28,10 +30,12 @@ export class EventEmitter<T> implements IEventEmitter<T> {
this._listeners.push(listener);
const disposable = {
dispose: () => {
for (let i = 0; i < this._listeners.length; i++) {
if (this._listeners[i] === listener) {
this._listeners.splice(i, 1);
return;
if (!this._disposed) {
for (let i = 0; i < this._listeners.length; i++) {
if (this._listeners[i] === listener) {
this._listeners.splice(i, 1);
return;
}
}
}
}
@@ -51,4 +55,11 @@ export class EventEmitter<T> implements IEventEmitter<T> {
queue[i].call(undefined, data);
}
}
public dispose(): void {
if (this._listeners) {
this._listeners.length = 0;
}
this._disposed = true;
}
}
+1
View File
@@ -29,6 +29,7 @@ export class Marker extends Disposable implements IMarker {
return;
}
this.isDisposed = true;
this.line = -1;
// Emit before super.dispose such that dispose listeners get a change to react
this._onDispose.fire();
}
+2 -18
View File
@@ -32,9 +32,9 @@ describe('CoreMouseService', () => {
const cms = new CoreMouseService(bufferService, coreService);
assert.deepEqual(Object.keys((cms as any)._protocols), ['NONE', 'X10', 'VT200', 'DRAG', 'ANY']);
});
it('default encodings - DEFAULT, UTF8, SGR, URXVT', () => {
it('default encodings - DEFAULT, SGR', () => {
const cms = new CoreMouseService(bufferService, coreService);
assert.deepEqual(Object.keys((cms as any)._encodings), ['DEFAULT', 'UTF8', 'SGR', 'URXVT']);
assert.deepEqual(Object.keys((cms as any)._encodings), ['DEFAULT', 'SGR']);
});
it('protocol/encoding setter, reset', () => {
const cms = new CoreMouseService(bufferService, coreService);
@@ -151,14 +151,6 @@ describe('CoreMouseService', () => {
}
}
});
it('UTF8 encoding', () => {
cms.activeProtocol = 'ANY';
cms.activeEncoding = 'UTF8';
for (let i = 0; i < bufferService.cols; ++i) {
assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);
}
});
it('SGR encoding', () => {
cms.activeProtocol = 'ANY';
cms.activeEncoding = 'SGR';
@@ -167,14 +159,6 @@ describe('CoreMouseService', () => {
assert.deepEqual(reports.pop(), `\x1b[<0;${i + 1};1M`);
}
});
it('URXVT', () => {
cms.activeProtocol = 'ANY';
cms.activeEncoding = 'URXVT';
for (let i = 0; i < bufferService.cols; ++i) {
assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
assert.deepEqual(reports.pop(), `\x1b[32;${i + 1};1M`);
}
});
});
it('eventCodes with modifiers (DEFAULT encoding)', () => {
// TODO: implement AUX button tests
+1 -20
View File
@@ -132,17 +132,6 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = {
// FIXED: params = params.map(v => (v > 255) ? 0 : value);
return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`;
},
/**
* UTF8 - CSI M Pb Px Py
* Same as DEFAULT, but with optional 2-byte UTF8
* encoding for values > 223 (can encode up to 2015).
*/
UTF8: (e: ICoreMouseEvent) => {
let params = [eventCode(e, false) + 32, e.col + 32, e.row + 32];
// limit to 2-byte UTF8
params = params.map(v => (v > 2047) ? 0 : v);
return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`;
},
/**
* SGR - CSI < Pb ; Px ; Py M|m
* No encoding limitation.
@@ -151,14 +140,6 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = {
SGR: (e: ICoreMouseEvent) => {
const final = (e.action === CoreMouseAction.UP && e.button !== CoreMouseButton.WHEEL) ? 'm' : 'M';
return `\x1b[<${eventCode(e, true)};${e.col};${e.row}${final}`;
},
/**
* URXVT - CSI Pb ; Px ; Py M
* Same button encoding as default, decimal encoding for coords.
* Ambiguity with other sequences, should not be used.
*/
URXVT: (e: ICoreMouseEvent) => {
return `\x1b[${eventCode(e, false) + 32};${e.col};${e.row}M`;
}
};
@@ -167,7 +148,7 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = {
*
* Provides mouse tracking reports with different protocols and encodings.
* - protocols: NONE (default), X10, VT200, DRAG, ANY
* - encodings: DEFAULT, SGR, UTF8, URXVT
* - encodings: DEFAULT, SGR (UTF8, URXVT removed in #2507)
*
* Custom protocols/encodings can be added by `addProtocol` / `addEncoding`.
* To activate a protocol/encoding, set `activeProtocol` / `activeEncoding`.
File diff suppressed because it is too large Load Diff
+26 -19
View File
@@ -49,7 +49,7 @@ declare module 'xterm' {
/**
* When enabled the cursor will be set to the beginning of the next line
* with every new line. This equivalent to sending '\r\n' for each '\n'.
* with every new line. This is equivalent to sending '\r\n' for each '\n'.
* Normally the termios settings of the underlying PTY deals with the
* translation of '\n' to '\r\n' and this setting should not be used. If you
* deal with data from a non-PTY related source, this settings might be
@@ -325,7 +325,8 @@ declare module 'xterm' {
/**
* Represents a specific line in the terminal that is tracked when scrollback
* is trimmed and lines are added or removed.
* is trimmed and lines are added or removed. This is a single line that may
* be part of a larger wrapped line.
*/
export interface IMarker extends IDisposable {
/**
@@ -339,7 +340,8 @@ declare module 'xterm' {
readonly isDisposed: boolean;
/**
* The actual line index in the buffer at this point in time.
* The actual line index in the buffer at this point in time. This is set to
* -1 if the marker has been disposed.
*/
readonly line: number;
}
@@ -435,7 +437,7 @@ declare module 'xterm' {
onData: IEvent<string>;
/**
* Adds an event listener for a key is pressed. The event value contains the
* Adds an event listener for when a key is pressed. The event value contains the
* string that will be sent in the data event as well as the DOM event that
* triggered it.
* @returns an `IDisposable` to stop listening.
@@ -607,7 +609,7 @@ declare module 'xterm' {
/**
* Selects text within the terminal.
* @param column The column the selection starts at..
* @param column The column the selection starts at.
* @param row The row the selection starts at.
* @param length The length of the selection.
*/
@@ -862,29 +864,34 @@ declare module 'xterm' {
*/
export interface IViewportRange {
/**
* The start cell of the range.
* The start of the range.
*/
start: IViewportCellPosition;
start: IViewportRangePosition;
/**
* The end cell of the range.
* The end of the range.
*/
end: IViewportCellPosition;
end: IViewportRangePosition;
}
/**
* An object representing a cell position within the viewport of the terminal.
*/
interface IViewportCellPosition {
interface IViewportRangePosition {
/**
* The column of the cell. Note that this is 1-based; the first column is column 1.
* The x position of the cell. This is a 0-based index that refers to the
* space in between columns, not the column itself. Index 0 refers to the
* left side of the viewport, index `Terminal.cols` refers to the right side
* of the viewport. This can be thought of as how a cursor is positioned in
* a text editor.
*/
col: number;
x: number;
/**
* The row of the cell. Note that this is 1-based; the first row is row 1.
* The y position of the cell. This is a 0-based index that refers to a
* specific row.
*/
row: number;
y: number;
}
/**
@@ -911,7 +918,7 @@ declare module 'xterm' {
/**
* The line within the buffer where the top of the bottom page is (when
* fully scrolled down);
* fully scrolled down).
*/
readonly baseY: number;
@@ -1040,7 +1047,7 @@ declare module 'xterm' {
* array will contain subarrays with their numercial values.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addCsiHandler or setCsiHandler).
* The most recently-added handler is tried first.
* The most recently added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable;
@@ -1059,7 +1066,7 @@ declare module 'xterm' {
* The function gets the payload and numerical parameters as arguments.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addDcsHandler or setDcsHandler).
* The most recently-added handler is tried first.
* The most recently added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable;
@@ -1072,7 +1079,7 @@ declare module 'xterm' {
* @param callback The function to handle the sequence.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addEscHandler or setEscHandler).
* The most recently-added handler is tried first.
* The most recently added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable;
@@ -1090,7 +1097,7 @@ declare module 'xterm' {
* The callback is called with OSC data string.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addOscHandler or setOscHandler).
* The most recently-added handler is tried first.
* The most recently added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;