File size: 6,062 Bytes
19605ab |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
/**
* Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
* Copyright (c) 2016, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
import { Socket } from 'net';
import { EventEmitter } from 'events';
import { ITerminal, IPtyForkOptions } from './interfaces';
import { EventEmitter2, IEvent } from './eventEmitter2';
import { IExitEvent } from './types';
export const DEFAULT_COLS: number = 80;
export const DEFAULT_ROWS: number = 24;
/**
* Default messages to indicate PAUSE/RESUME for automatic flow control.
* To avoid conflicts with rebound XON/XOFF control codes (such as on-my-zsh),
* the sequences can be customized in `IPtyForkOptions`.
*/
const FLOW_CONTROL_PAUSE = '\x13'; // defaults to XOFF
const FLOW_CONTROL_RESUME = '\x11'; // defaults to XON
export abstract class Terminal implements ITerminal {
protected _socket: Socket;
protected _pid: number;
protected _fd: number;
protected _pty: any;
protected _file: string;
protected _name: string;
protected _cols: number;
protected _rows: number;
protected _readable: boolean;
protected _writable: boolean;
protected _internalee: EventEmitter;
private _flowControlPause: string;
private _flowControlResume: string;
public handleFlowControl: boolean;
private _onData = new EventEmitter2<string>();
public get onData(): IEvent<string> { return this._onData.event; }
private _onExit = new EventEmitter2<IExitEvent>();
public get onExit(): IEvent<IExitEvent> { return this._onExit.event; }
public get pid(): number { return this._pid; }
public get cols(): number { return this._cols; }
public get rows(): number { return this._rows; }
constructor(opt?: IPtyForkOptions) {
// for 'close'
this._internalee = new EventEmitter();
if (!opt) {
return;
}
// Do basic type checks here in case node-pty is being used within JavaScript. If the wrong
// types go through to the C++ side it can lead to hard to diagnose exceptions.
this._checkType('name', opt.name ? opt.name : null, 'string');
this._checkType('cols', opt.cols ? opt.cols : null, 'number');
this._checkType('rows', opt.rows ? opt.rows : null, 'number');
this._checkType('cwd', opt.cwd ? opt.cwd : null, 'string');
this._checkType('env', opt.env ? opt.env : null, 'object');
this._checkType('uid', opt.uid ? opt.uid : null, 'number');
this._checkType('gid', opt.gid ? opt.gid : null, 'number');
this._checkType('encoding', opt.encoding ? opt.encoding : null, 'string');
// setup flow control handling
this.handleFlowControl = !!(opt.handleFlowControl);
this._flowControlPause = opt.flowControlPause || FLOW_CONTROL_PAUSE;
this._flowControlResume = opt.flowControlResume || FLOW_CONTROL_RESUME;
}
protected abstract _write(data: string): void;
public write(data: string): void {
if (this.handleFlowControl) {
// PAUSE/RESUME messages are not forwarded to the pty
if (data === this._flowControlPause) {
this.pause();
return;
}
if (data === this._flowControlResume) {
this.resume();
return;
}
}
// everything else goes to the real pty
this._write(data);
}
protected _forwardEvents(): void {
this.on('data', e => this._onData.fire(e));
this.on('exit', (exitCode, signal) => this._onExit.fire({ exitCode, signal }));
}
private _checkType(name: string, value: any, type: string): void {
if (value && typeof value !== type) {
throw new Error(`${name} must be a ${type} (not a ${typeof value})`);
}
}
/** See net.Socket.end */
public end(data: string): void {
this._socket.end(data);
}
/** See stream.Readable.pipe */
public pipe(dest: any, options: any): any {
return this._socket.pipe(dest, options);
}
/** See net.Socket.pause */
public pause(): Socket {
return this._socket.pause();
}
/** See net.Socket.resume */
public resume(): Socket {
return this._socket.resume();
}
/** See net.Socket.setEncoding */
public setEncoding(encoding: string | null): void {
if ((<any>this._socket)._decoder) {
delete (<any>this._socket)._decoder;
}
if (encoding) {
this._socket.setEncoding(encoding);
}
}
public addListener(eventName: string, listener: (...args: any[]) => any): void { this.on(eventName, listener); }
public on(eventName: string, listener: (...args: any[]) => any): void {
if (eventName === 'close') {
this._internalee.on('close', listener);
return;
}
this._socket.on(eventName, listener);
}
public emit(eventName: string, ...args: any[]): any {
if (eventName === 'close') {
return this._internalee.emit.apply(this._internalee, arguments);
}
return this._socket.emit.apply(this._socket, arguments);
}
public listeners(eventName: string): Function[] {
return this._socket.listeners(eventName);
}
public removeListener(eventName: string, listener: (...args: any[]) => any): void {
this._socket.removeListener(eventName, listener);
}
public removeAllListeners(eventName: string): void {
this._socket.removeAllListeners(eventName);
}
public once(eventName: string, listener: (...args: any[]) => any): void {
this._socket.once(eventName, listener);
}
public abstract resize(cols: number, rows: number): void;
public abstract destroy(): void;
public abstract kill(signal?: string): void;
public abstract get process(): string;
public abstract get master(): Socket;
public abstract get slave(): Socket;
protected _close(): void {
this._socket.writable = false;
this._socket.readable = false;
this.write = () => {};
this.end = () => {};
this._writable = false;
this._readable = false;
}
protected _parseEnv(env: {[key: string]: string}): string[] {
const keys = Object.keys(env || {});
const pairs = [];
for (let i = 0; i < keys.length; i++) {
pairs.push(keys[i] + '=' + env[keys[i]]);
}
return pairs;
}
}
|