mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge branch 'master' into window_manipulation
This commit is contained in:
@@ -33,6 +33,7 @@ export class AttachAddon implements ITerminalAddon {
|
||||
|
||||
if (this._bidirectional) {
|
||||
this._disposables.push(terminal.onData(data => this._sendData(data)));
|
||||
this._disposables.push(terminal.onBinary(data => this._sendBinary(data)));
|
||||
}
|
||||
|
||||
this._disposables.push(addSocketListener(this._socket, 'close', () => this.dispose()));
|
||||
@@ -51,6 +52,17 @@ export class AttachAddon implements ITerminalAddon {
|
||||
}
|
||||
this._socket.send(data);
|
||||
}
|
||||
|
||||
private _sendBinary(data: string): void {
|
||||
if (this._socket.readyState !== 1) {
|
||||
return;
|
||||
}
|
||||
const buffer = new Uint8Array(data.length);
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
buffer[i] = data.charCodeAt(i) & 255;
|
||||
}
|
||||
this._socket.send(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
function addSocketListener<K extends keyof WebSocketEventMap>(socket: WebSocket, type: K, handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any): IDisposable {
|
||||
|
||||
@@ -167,6 +167,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
|
||||
private _onData = new EventEmitter<string>();
|
||||
public get onData(): IEvent<string> { return this._onData.event; }
|
||||
private _onBinary = new EventEmitter<string>();
|
||||
public get onBinary(): IEvent<string> { return this._onBinary.event; }
|
||||
private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>();
|
||||
public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; }
|
||||
private _onLineFeed = new EventEmitter<void>();
|
||||
@@ -219,6 +221,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
|
||||
this._coreService = this._instantiationService.createInstance(CoreService, () => this.scrollToBottom());
|
||||
this._instantiationService.setService(ICoreService, this._coreService);
|
||||
this._coreService.onData(e => this._onData.fire(e));
|
||||
this._coreService.onBinary(e => this._onBinary.fire(e));
|
||||
this._coreMouseService = this._instantiationService.createInstance(CoreMouseService);
|
||||
this._instantiationService.setService(ICoreMouseService, this._coreMouseService);
|
||||
this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService);
|
||||
|
||||
@@ -32,6 +32,7 @@ export class MockTerminal implements ITerminal {
|
||||
onLineFeed: IEvent<void>;
|
||||
onSelectionChange: IEvent<void>;
|
||||
onData: IEvent<string>;
|
||||
onBinary: IEvent<string>;
|
||||
onTitleChange: IEvent<string>;
|
||||
onScroll: IEvent<number>;
|
||||
onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>;
|
||||
|
||||
Vendored
+1
@@ -179,6 +179,7 @@ export interface IPublicTerminal extends IDisposable {
|
||||
markers: IMarker[];
|
||||
onCursorMove: IEvent<void>;
|
||||
onData: IEvent<string>;
|
||||
onBinary: IEvent<string>;
|
||||
onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;
|
||||
onLineFeed: IEvent<void>;
|
||||
onScroll: IEvent<number>;
|
||||
|
||||
@@ -50,8 +50,10 @@ export class MockCoreService implements ICoreService {
|
||||
decPrivateModes: IDecPrivateModes = {} as any;
|
||||
onData: IEvent<string> = new EventEmitter<string>().event;
|
||||
onUserInput: IEvent<void> = new EventEmitter<void>().event;
|
||||
onBinary: IEvent<string> = new EventEmitter<string>().event;
|
||||
reset(): void {}
|
||||
triggerDataEvent(data: string, wasUserInput?: boolean): void {}
|
||||
triggerBinaryEvent(data: string): void {}
|
||||
}
|
||||
|
||||
export class MockDirtyRowService implements IDirtyRowService {
|
||||
|
||||
Vendored
+3
@@ -249,6 +249,9 @@ export interface ICoreMouseProtocol {
|
||||
* The tracking encoding can be registered and activated at the CoreMouseService.
|
||||
* If a ICoreMouseEvent passes all procotol restrictions it will be encoded
|
||||
* with the active encoding and sent out.
|
||||
* Note: Returning an empty string will supress sending a mouse report,
|
||||
* which can be used to skip creating falsey reports in limited encodings
|
||||
* (DEFAULT only supports up to 223 1-based as coord value).
|
||||
*/
|
||||
export type CoreMouseEncoding = (event: ICoreMouseEvent) => string;
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ describe('CoreMouseService', () => {
|
||||
cms = new CoreMouseService(bufferService, coreService);
|
||||
reports = [];
|
||||
coreService.triggerDataEvent = (data: string, userInput?: boolean) => reports.push(data);
|
||||
coreService.triggerBinaryEvent = (data: string) => reports.push(data);
|
||||
});
|
||||
it('NONE', () => {
|
||||
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
|
||||
@@ -143,11 +144,11 @@ describe('CoreMouseService', () => {
|
||||
cms.activeProtocol = 'ANY';
|
||||
for (let i = 0; i < bufferService.cols; ++i) {
|
||||
assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
|
||||
// capped at 95
|
||||
if (i < 95) {
|
||||
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);
|
||||
if (i > 222) {
|
||||
// supress mouse reports if we are out of addressible range (max. 222)
|
||||
assert.deepEqual(toBytes(reports.pop()), []);
|
||||
} else {
|
||||
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, 0x7f, 0x21]);
|
||||
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -121,15 +121,17 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = {
|
||||
/**
|
||||
* DEFAULT - CSI M Pb Px Py
|
||||
* Single byte encoding for coords and event code.
|
||||
* Can encode values up to 223. The Encoding of higher
|
||||
* values is not UTF-8 compatible (and currently limited
|
||||
* to 95 in xterm.js).
|
||||
* Can encode values up to 223 (1-based).
|
||||
*/
|
||||
DEFAULT: (e: ICoreMouseEvent) => {
|
||||
let params = [eventCode(e, false) + 32, e.col + 32, e.row + 32];
|
||||
// FIXME: we are currently limited to ASCII range
|
||||
params = params.map(v => (v > 127) ? 127 : v);
|
||||
// FIXED: params = params.map(v => (v > 255) ? 0 : value);
|
||||
const params = [eventCode(e, false) + 32, e.col + 32, e.row + 32];
|
||||
// supress mouse report if we exceed addressible range
|
||||
// Note this is handled differently by emulators
|
||||
// - xterm: sends 0;0 coords instead
|
||||
// - vte, konsole: no report
|
||||
if (params[0] > 255 || params[1] > 255 || params[2] > 255) {
|
||||
return '';
|
||||
}
|
||||
return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`;
|
||||
},
|
||||
/**
|
||||
@@ -266,7 +268,14 @@ export class CoreMouseService implements ICoreMouseService {
|
||||
|
||||
// encode report and send
|
||||
const report = this._encodings[this._activeEncoding](e);
|
||||
this._coreService.triggerDataEvent(report, true);
|
||||
if (report) {
|
||||
// always send DEFAULT as binary data
|
||||
if (this._activeEncoding === 'DEFAULT') {
|
||||
this._coreService.triggerBinaryEvent(report);
|
||||
} else {
|
||||
this._coreService.triggerDataEvent(report, true);
|
||||
}
|
||||
}
|
||||
|
||||
this._lastEvent = e;
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ export class CoreService implements ICoreService {
|
||||
public get onData(): IEvent<string> { return this._onData.event; }
|
||||
private _onUserInput = new EventEmitter<void>();
|
||||
public get onUserInput(): IEvent<void> { return this._onUserInput.event; }
|
||||
private _onBinary = new EventEmitter<string>();
|
||||
public get onBinary(): IEvent<string> { return this._onBinary.event; }
|
||||
|
||||
constructor(
|
||||
// TODO: Move this into a service
|
||||
@@ -59,4 +61,12 @@ export class CoreService implements ICoreService {
|
||||
this._logService.debug(`sending data "${data}"`, () => data.split('').map(e => e.charCodeAt(0)));
|
||||
this._onData.fire(data);
|
||||
}
|
||||
|
||||
public triggerBinaryEvent(data: string): void {
|
||||
if (this._optionsService.options.disableStdin) {
|
||||
return;
|
||||
}
|
||||
this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0)));
|
||||
this._onBinary.fire(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ export interface ICoreService {
|
||||
|
||||
readonly onData: IEvent<string>;
|
||||
readonly onUserInput: IEvent<void>;
|
||||
readonly onBinary: IEvent<string>;
|
||||
|
||||
reset(): void;
|
||||
|
||||
@@ -78,8 +79,14 @@ export interface ICoreService {
|
||||
* resulting from parsing incoming data). When true this will also:
|
||||
* - Scroll to the bottom of the buffer.s
|
||||
* - Fire the `onUserInput` event (so selection can be cleared).
|
||||
*/
|
||||
*/
|
||||
triggerDataEvent(data: string, wasUserInput?: boolean): void;
|
||||
|
||||
/**
|
||||
* Triggers the onBinary event in the public API.
|
||||
* @param data The data that is being emitted.
|
||||
*/
|
||||
triggerBinaryEvent(data: string): void;
|
||||
}
|
||||
|
||||
export const IDirtyRowService = createDecorator<IDirtyRowService>('DirtyRowService');
|
||||
|
||||
@@ -27,6 +27,7 @@ export class Terminal implements ITerminalApi {
|
||||
public get onLineFeed(): IEvent<void> { return this._core.onLineFeed; }
|
||||
public get onSelectionChange(): IEvent<void> { return this._core.onSelectionChange; }
|
||||
public get onData(): IEvent<string> { return this._core.onData; }
|
||||
public get onBinary(): IEvent<string> { return this._core.onBinary; }
|
||||
public get onTitleChange(): IEvent<string> { return this._core.onTitleChange; }
|
||||
public get onScroll(): IEvent<number> { return this._core.onScroll; }
|
||||
public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._core.onKey; }
|
||||
|
||||
@@ -220,6 +220,7 @@ describe('Mouse Tracking Tests', () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.term.onData(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) ));
|
||||
window.term.onBinary(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) ));
|
||||
window.term.setOption('fontSize', ${fontSize});
|
||||
window.term.resize(${cols}, ${rows});
|
||||
`);
|
||||
@@ -255,12 +256,17 @@ describe('Mouse Tracking Tests', () => {
|
||||
await pollFor(page, () => getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
|
||||
|
||||
// test at max rows/cols
|
||||
// bug: we are capped at col 95 currently
|
||||
// fix: allow values up to 223, any bigger should drop to 0
|
||||
await mouseMove(cols - 1, rows - 1);
|
||||
// capped at 223 (1-based)
|
||||
await mouseMove(223 - 1, rows - 1);
|
||||
await mouseDown('left');
|
||||
await mouseUp('left');
|
||||
await pollFor(page, () => getReports(encoding), [{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
|
||||
await pollFor(page, () => getReports(encoding), [{col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
|
||||
|
||||
// higher than 223 should not report at all
|
||||
await mouseMove(257, rows - 1);
|
||||
await mouseDown('left');
|
||||
await mouseUp('left');
|
||||
await pollFor(page, () => getReports(encoding), []);
|
||||
|
||||
// button press/move/release tests
|
||||
// left button
|
||||
@@ -511,14 +517,13 @@ describe('Mouse Tracking Tests', () => {
|
||||
]);
|
||||
|
||||
// test at max rows/cols
|
||||
// bug: we are capped at col 95 currently
|
||||
// fix: allow values up to 223, any bigger should drop to 0
|
||||
await mouseMove(cols - 1, rows - 1);
|
||||
// capped at 223 (1-based)
|
||||
await mouseMove(223 - 1, rows - 1);
|
||||
await mouseDown('left');
|
||||
await mouseUp('left');
|
||||
await pollFor(page, () => getReports(encoding), [
|
||||
{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
|
||||
{col: 95, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
|
||||
{col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
|
||||
{col: 223, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
|
||||
]);
|
||||
|
||||
// button press/move/release tests
|
||||
@@ -821,14 +826,13 @@ describe('Mouse Tracking Tests', () => {
|
||||
]);
|
||||
|
||||
// test at max rows/cols
|
||||
// bug: we are capped at col 95 currently
|
||||
// fix: allow values up to 223, any bigger should drop to 0
|
||||
await mouseMove(cols - 1, rows - 1);
|
||||
// capped at 223 (1-based)
|
||||
await mouseMove(223 - 1, rows - 1);
|
||||
await mouseDown('left');
|
||||
await mouseUp('left');
|
||||
await pollFor(page, () => getReports(encoding), [
|
||||
{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
|
||||
{col: 95, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
|
||||
{col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
|
||||
{col: 223, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
|
||||
]);
|
||||
|
||||
// button press/move/release tests
|
||||
@@ -1142,15 +1146,14 @@ describe('Mouse Tracking Tests', () => {
|
||||
]);
|
||||
|
||||
// test at max rows/cols
|
||||
// bug: we are capped at col 95 currently
|
||||
// fix: allow values up to 223, any bigger should drop to 0
|
||||
await mouseMove(cols - 1, rows - 1);
|
||||
// capped at 223 (1-based)
|
||||
await mouseMove(223 - 1, rows - 1);
|
||||
await mouseDown('left');
|
||||
await mouseUp('left');
|
||||
await pollFor(page, () => getReports(encoding), [
|
||||
{col: 95, row: rows, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: false}}},
|
||||
{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
|
||||
{col: 95, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
|
||||
{col: 223, row: rows, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: false}}},
|
||||
{col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
|
||||
{col: 223, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
|
||||
]);
|
||||
|
||||
// button press/move/release tests
|
||||
|
||||
Vendored
+11
@@ -554,6 +554,17 @@ declare module 'xterm' {
|
||||
*/
|
||||
constructor(options?: ITerminalOptions);
|
||||
|
||||
/**
|
||||
* Adds an event listener for when a binary event fires. This is used to
|
||||
* enable non UTF-8 conformant binary messages to be sent to the backend.
|
||||
* Currently this is only used for a certain type of mouse reports that
|
||||
* happen to be not UTF-8 compatible.
|
||||
* The event value is a JS string, pass it to the underlying pty as
|
||||
* binary data, e.g. `pty.write(Buffer.from(data, 'binary'))`.
|
||||
* @returns an `IDisposable` to stop listening.
|
||||
*/
|
||||
onBinary: IEvent<string>;
|
||||
|
||||
/**
|
||||
* Adds an event listener for the cursor moves.
|
||||
* @returns an `IDisposable` to stop listening.
|
||||
|
||||
Reference in New Issue
Block a user