Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IoService #2362

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions addons/xterm-addon-attach/src/AttachAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,29 @@ import { Terminal, IDisposable, ITerminalAddon } from 'xterm';

interface IAttachOptions {
bidirectional?: boolean;
inputUtf8?: boolean;
}

export class AttachAddon implements ITerminalAddon {
private _socket: WebSocket;
private _bidirectional: boolean;
private _utf8: boolean;
private _disposables: IDisposable[] = [];

constructor(socket: WebSocket, options?: IAttachOptions) {
this._socket = socket;
// always set binary type to arraybuffer, we do not handle blobs
this._socket.binaryType = 'arraybuffer';
this._bidirectional = (options && options.bidirectional === false) ? false : true;
this._utf8 = !!(options && options.inputUtf8);
}

public activate(terminal: Terminal): void {
if (this._utf8) {
this._disposables.push(addSocketListener(this._socket, 'message',
(ev: MessageEvent | Event | CloseEvent) => terminal.writeUtf8(new Uint8Array((ev as any).data as ArrayBuffer))));
} else {
this._disposables.push(addSocketListener(this._socket, 'message',
(ev: MessageEvent | Event | CloseEvent) => terminal.write((ev as any).data as string)));
}
this._disposables.push(
addSocketListener(this._socket, 'message',
(ev: MessageEvent | Event | CloseEvent) => {
const data = (ev as MessageEvent).data;
terminal.write(typeof data === 'string' ? data : new Uint8Array(data));
}
)
);

if (this._bidirectional) {
this._disposables.push(terminal.onData(data => this._sendData(data)));
Expand All @@ -47,7 +45,7 @@ export class AttachAddon implements ITerminalAddon {
this._disposables.forEach(d => d.dispose());
}

private _sendData(data: string): void {
private _sendData(data: Uint8Array): void {
// TODO: do something better than just swallowing
// the data if the socket is not in a working condition
if (this._socket.readyState !== 1) {
Expand Down
8 changes: 0 additions & 8 deletions addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@ declare module 'xterm-addon-attach' {
* Whether input should be written to the backend. Defaults to `true`.
*/
bidirectional?: boolean;

/**
* Whether to use UTF8 binary transport for incoming messages. Defaults to `false`.
* Note: This must be in line with the server side of the websocket.
* Always send string messages from the backend if this options is false,
* otherwise always binary UTF8 data.
*/
inputUtf8?: boolean;
}

export class AttachAddon implements ITerminalAddon {
Expand Down
2 changes: 1 addition & 1 deletion addons/xterm-addon-search/src/SearchAddon.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
async function writeSync(data: string): Promise<void> {
await page.evaluate(`window.term.write('${data}');`);
while (true) {
if (await page.evaluate(`window.term._core.writeBuffer.length === 0`)) {
if (await page.evaluate(`window.term._core._ioService._writeBuffer.length === 0`)) {
break;
}
}
Expand Down
2 changes: 1 addition & 1 deletion addons/xterm-addon-webgl/src/WebglRenderer.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
async function writeSync(data: string): Promise<void> {
await page.evaluate(`window.term.write('${data}');`);
while (true) {
if (await page.evaluate(`window.term._core.writeBuffer.length === 0`)) {
if (await page.evaluate(`window.term._core._ioService._writeBuffer.length === 0`)) {
break;
}
}
Expand Down
10 changes: 2 additions & 8 deletions demo/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,7 @@ function createTerminal(): void {
}

function runRealTerminal(): void {
/**
* The demo defaults to string transport by default.
* To run it with UTF8 binary transport, swap comment on
* the lines below. (Must also be switched in server.js)
*/
term.loadAddon(new AttachAddon(socket));
// term.loadAddon(new AttachAddon(socket, {inputUtf8: true}));

term._initialized = true;
}

Expand Down Expand Up @@ -233,7 +226,8 @@ function initOptions(term: TerminalType): void {
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
logLevel: ['debug', 'info', 'warn', 'error', 'off'],
rendererType: ['dom', 'canvas'],
wordSeparator: null
wordSeparator: null,
encoding: Object.keys(term.encodings)
};
const options = Object.keys((<any>term)._core.options);
const booleanOptions = [];
Expand Down
9 changes: 4 additions & 5 deletions demo/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ var os = require('os');
var pty = require('node-pty');

/**
* Whether to use UTF8 binary transport.
* (Must also be switched in client.ts)
* Whether to use binary transport.
*/
const USE_BINARY_UTF8 = false;
const USE_BINARY = true;


function startServer() {
Expand Down Expand Up @@ -47,7 +46,7 @@ function startServer() {
rows: rows || 24,
cwd: env.PWD,
env: env,
encoding: USE_BINARY_UTF8 ? null : 'utf8'
encoding: USE_BINARY ? null : 'utf8'
});

console.log('Created terminal with PID: ' + term.pid);
Expand Down Expand Up @@ -109,7 +108,7 @@ function startServer() {
}
};
}
const send = USE_BINARY_UTF8 ? bufferUtf8(ws, 5) : buffer(ws, 5);
const send = USE_BINARY ? bufferUtf8(ws, 5) : buffer(ws, 5);

term.on('data', function(data) {
try {
Expand Down
66 changes: 37 additions & 29 deletions src/InputHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsSer
import { IBufferService } from 'common/services/Services';
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
import { clone } from 'common/Clone';
import { IInputHandler } from 'Types';
import { StringToUtf32 } from 'common/input/Encodings';

function getCursor(term: TestTerminal): number[] {
return [
Expand All @@ -25,6 +27,12 @@ function getCursor(term: TestTerminal): number[] {
];
}

function parse(ih: IInputHandler, s: string): void {
const buffer = new Uint32Array(s.length);
const decoder = new StringToUtf32();
ih.parse(buffer, decoder.decode(s, buffer));
}

describe('InputHandler', () => {
describe('save and restore cursor', () => {
const terminal = new MockInputHandlingTerminal();
Expand Down Expand Up @@ -115,10 +123,10 @@ describe('InputHandler', () => {
const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());

// insert some data in first and second line
inputHandler.parse(Array(bufferService.cols - 9).join('a'));
inputHandler.parse('1234567890');
inputHandler.parse(Array(bufferService.cols - 9).join('a'));
inputHandler.parse('1234567890');
parse(inputHandler, Array(bufferService.cols - 9).join('a'));
parse(inputHandler, '1234567890');
parse(inputHandler, Array(bufferService.cols - 9).join('a'));
parse(inputHandler, '1234567890');
const line1: IBufferLine = bufferService.buffer.lines.get(0);
expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '1234567890');

Expand Down Expand Up @@ -153,10 +161,10 @@ describe('InputHandler', () => {
const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());

// insert some data in first and second line
inputHandler.parse(Array(bufferService.cols - 9).join('a'));
inputHandler.parse('1234567890');
inputHandler.parse(Array(bufferService.cols - 9).join('a'));
inputHandler.parse('1234567890');
parse(inputHandler, Array(bufferService.cols - 9).join('a'));
parse(inputHandler, '1234567890');
parse(inputHandler, Array(bufferService.cols - 9).join('a'));
parse(inputHandler, '1234567890');
const line1: IBufferLine = bufferService.buffer.lines.get(0);
expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '1234567890');

Expand Down Expand Up @@ -194,9 +202,9 @@ describe('InputHandler', () => {
const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());

// fill 6 lines to test 3 different states
inputHandler.parse(Array(bufferService.cols + 1).join('a'));
inputHandler.parse(Array(bufferService.cols + 1).join('a'));
inputHandler.parse(Array(bufferService.cols + 1).join('a'));
parse(inputHandler, Array(bufferService.cols + 1).join('a'));
parse(inputHandler, Array(bufferService.cols + 1).join('a'));
parse(inputHandler, Array(bufferService.cols + 1).join('a'));

// params[0] - right erase
bufferService.buffer.y = 0;
Expand All @@ -223,7 +231,7 @@ describe('InputHandler', () => {
const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());

// fill display with a's
for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a'));
for (let i = 0; i < bufferService.rows; ++i) parse(inputHandler, Array(bufferService.cols + 1).join('a'));

// params [0] - right and below erase
bufferService.buffer.y = 5;
Expand Down Expand Up @@ -251,7 +259,7 @@ describe('InputHandler', () => {
// reset
bufferService.buffer.y = 0;
bufferService.buffer.x = 0;
for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a'));
for (let i = 0; i < bufferService.rows; ++i) parse(inputHandler, Array(bufferService.cols + 1).join('a'));

// params [1] - left and above
bufferService.buffer.y = 5;
Expand Down Expand Up @@ -279,7 +287,7 @@ describe('InputHandler', () => {
// reset
bufferService.buffer.y = 0;
bufferService.buffer.x = 0;
for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a'));
for (let i = 0; i < bufferService.rows; ++i) parse(inputHandler, Array(bufferService.cols + 1).join('a'));

// params [2] - whole screen
bufferService.buffer.y = 5;
Expand Down Expand Up @@ -307,9 +315,9 @@ describe('InputHandler', () => {
// reset and add a wrapped line
bufferService.buffer.y = 0;
bufferService.buffer.x = 0;
inputHandler.parse(Array(bufferService.cols + 1).join('a')); // line 0
inputHandler.parse(Array(bufferService.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a'));
parse(inputHandler, Array(bufferService.cols + 1).join('a')); // line 0
parse(inputHandler, Array(bufferService.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < bufferService.rows; ++i) parse(inputHandler, Array(bufferService.cols + 1).join('a'));

// params[1] left and above with wrap
// confirm precondition that line 2 is wrapped
Expand All @@ -322,9 +330,9 @@ describe('InputHandler', () => {
// reset and add a wrapped line
bufferService.buffer.y = 0;
bufferService.buffer.x = 0;
inputHandler.parse(Array(bufferService.cols + 1).join('a')); // line 0
inputHandler.parse(Array(bufferService.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a'));
parse(inputHandler, Array(bufferService.cols + 1).join('a')); // line 0
parse(inputHandler, Array(bufferService.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < bufferService.rows; ++i) parse(inputHandler, Array(bufferService.cols + 1).join('a'));

// params[1] left and above with wrap
// confirm precondition that line 2 is wrapped
Expand All @@ -338,15 +346,15 @@ describe('InputHandler', () => {
it('convertEol setting', function(): void {
// not converting
const termNotConverting = new Terminal({cols: 15, rows: 10});
(termNotConverting as any)._inputHandler.parse('Hello\nWorld');
parse((termNotConverting as any)._inputHandler, 'Hello\nWorld');
expect(termNotConverting.buffer.lines.get(0).translateToString(false)).equals('Hello ');
expect(termNotConverting.buffer.lines.get(1).translateToString(false)).equals(' World ');
expect(termNotConverting.buffer.lines.get(0).translateToString(true)).equals('Hello');
expect(termNotConverting.buffer.lines.get(1).translateToString(true)).equals(' World');

// converting
const termConverting = new Terminal({cols: 15, rows: 10, convertEol: true});
(termConverting as any)._inputHandler.parse('Hello\nWorld');
parse((termConverting as any)._inputHandler, 'Hello\nWorld');
expect(termConverting.buffer.lines.get(0).translateToString(false)).equals('Hello ');
expect(termConverting.buffer.lines.get(1).translateToString(false)).equals('World ');
expect(termConverting.buffer.lines.get(0).translateToString(true)).equals('Hello');
Expand All @@ -373,21 +381,21 @@ describe('InputHandler', () => {
handler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
});
it('should handle DECSET/DECRST 47 (alt screen buffer)', () => {
handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST');
parse(handler, '\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST');
expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('');
expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(' TEST');
// Text color of 'TEST' should be red
expect((bufferService.buffer.lines.get(1).loadCell(4, new CellData()).getFgColor())).to.equal(1);
});
it('should handle DECSET/DECRST 1047 (alt screen buffer)', () => {
handler.parse('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST');
parse(handler, '\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST');
expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('');
expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(' TEST');
// Text color of 'TEST' should be red
expect((bufferService.buffer.lines.get(1).loadCell(4, new CellData()).getFgColor())).to.equal(1);
});
it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => {
handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST');
parse(handler, '\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST');
expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST');
expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal('JUNK');
// Text color of 'TEST' should be default
Expand All @@ -396,24 +404,24 @@ describe('InputHandler', () => {
expect((bufferService.buffer.lines.get(1).loadCell(0, new CellData()).getFgColor())).to.equal(1);
});
it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => {
handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST');
parse(handler, '\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST');
expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST');
expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal('');
// Text color of 'TEST' should be default
expect(bufferService.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg);
});
it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', () => {
handler.parse('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST');
parse(handler, '\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST');
expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST');
// Text color of 'TEST' should be default
expect(bufferService.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg);
handler.parse('\x1b[?1049h\x1b[uTEST');
parse(handler, '\x1b[?1049h\x1b[uTEST');
expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal('TEST');
// Text color of 'TEST' should be red
expect((bufferService.buffer.lines.get(1).loadCell(0, new CellData()).getFgColor())).to.equal(1);
});
it('should handle DECSET/DECRST 1049 - clears alt buffer with erase attributes', () => {
handler.parse('\x1b[42m\x1b[?1049h');
parse(handler, '\x1b[42m\x1b[?1049h');
// Buffer should be filled with green background
expect(bufferService.buffer.lines.get(20).loadCell(10, new CellData()).getBgColor()).to.equal(2);
});
Expand Down
Loading