2 * Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
3 * Copyright (c) 2016, Daniel Imms (MIT License).
4 * Copyright (c) 2018, Microsoft Corporation (MIT License).
7 import { Socket } from 'net';
8 import { EventEmitter } from 'events';
9 import { ITerminal, IPtyForkOptions } from './interfaces';
10 import { EventEmitter2, IEvent } from './eventEmitter2';
11 import { IExitEvent } from './types';
13 export const DEFAULT_COLS: number = 80;
14 export const DEFAULT_ROWS: number = 24;
17 * Default messages to indicate PAUSE/RESUME for automatic flow control.
18 * To avoid conflicts with rebound XON/XOFF control codes (such as on-my-zsh),
19 * the sequences can be customized in `IPtyForkOptions`.
21 const FLOW_CONTROL_PAUSE = '\x13'; // defaults to XOFF
22 const FLOW_CONTROL_RESUME = '\x11'; // defaults to XON
24 export abstract class Terminal implements ITerminal {
25 protected _socket: Socket;
26 protected _pid: number;
27 protected _fd: number;
30 protected _file: string;
31 protected _name: string;
32 protected _cols: number;
33 protected _rows: number;
35 protected _readable: boolean;
36 protected _writable: boolean;
38 protected _internalee: EventEmitter;
39 private _flowControlPause: string;
40 private _flowControlResume: string;
41 public handleFlowControl: boolean;
43 private _onData = new EventEmitter2<string>();
44 public get onData(): IEvent<string> { return this._onData.event; }
45 private _onExit = new EventEmitter2<IExitEvent>();
46 public get onExit(): IEvent<IExitEvent> { return this._onExit.event; }
48 public get pid(): number { return this._pid; }
49 public get cols(): number { return this._cols; }
50 public get rows(): number { return this._rows; }
52 constructor(opt?: IPtyForkOptions) {
54 this._internalee = new EventEmitter();
60 // Do basic type checks here in case node-pty is being used within JavaScript. If the wrong
61 // types go through to the C++ side it can lead to hard to diagnose exceptions.
62 this._checkType('name', opt.name ? opt.name : null, 'string');
63 this._checkType('cols', opt.cols ? opt.cols : null, 'number');
64 this._checkType('rows', opt.rows ? opt.rows : null, 'number');
65 this._checkType('cwd', opt.cwd ? opt.cwd : null, 'string');
66 this._checkType('env', opt.env ? opt.env : null, 'object');
67 this._checkType('uid', opt.uid ? opt.uid : null, 'number');
68 this._checkType('gid', opt.gid ? opt.gid : null, 'number');
69 this._checkType('encoding', opt.encoding ? opt.encoding : null, 'string');
71 // setup flow control handling
72 this.handleFlowControl = !!(opt.handleFlowControl);
73 this._flowControlPause = opt.flowControlPause || FLOW_CONTROL_PAUSE;
74 this._flowControlResume = opt.flowControlResume || FLOW_CONTROL_RESUME;
77 protected abstract _write(data: string): void;
79 public write(data: string): void {
80 if (this.handleFlowControl) {
81 // PAUSE/RESUME messages are not forwarded to the pty
82 if (data === this._flowControlPause) {
86 if (data === this._flowControlResume) {
91 // everything else goes to the real pty
95 protected _forwardEvents(): void {
96 this.on('data', e => this._onData.fire(e));
97 this.on('exit', (exitCode, signal) => this._onExit.fire({ exitCode, signal }));
100 private _checkType(name: string, value: any, type: string): void {
101 if (value && typeof value !== type) {
102 throw new Error(`${name} must be a ${type} (not a ${typeof value})`);
106 /** See net.Socket.end */
107 public end(data: string): void {
108 this._socket.end(data);
111 /** See stream.Readable.pipe */
112 public pipe(dest: any, options: any): any {
113 return this._socket.pipe(dest, options);
116 /** See net.Socket.pause */
117 public pause(): Socket {
118 return this._socket.pause();
121 /** See net.Socket.resume */
122 public resume(): Socket {
123 return this._socket.resume();
126 /** See net.Socket.setEncoding */
127 public setEncoding(encoding: string | null): void {
128 if ((<any>this._socket)._decoder) {
129 delete (<any>this._socket)._decoder;
132 this._socket.setEncoding(encoding);
136 public addListener(eventName: string, listener: (...args: any[]) => any): void { this.on(eventName, listener); }
137 public on(eventName: string, listener: (...args: any[]) => any): void {
138 if (eventName === 'close') {
139 this._internalee.on('close', listener);
142 this._socket.on(eventName, listener);
145 public emit(eventName: string, ...args: any[]): any {
146 if (eventName === 'close') {
147 return this._internalee.emit.apply(this._internalee, arguments);
149 return this._socket.emit.apply(this._socket, arguments);
152 public listeners(eventName: string): Function[] {
153 return this._socket.listeners(eventName);
156 public removeListener(eventName: string, listener: (...args: any[]) => any): void {
157 this._socket.removeListener(eventName, listener);
160 public removeAllListeners(eventName: string): void {
161 this._socket.removeAllListeners(eventName);
164 public once(eventName: string, listener: (...args: any[]) => any): void {
165 this._socket.once(eventName, listener);
168 public abstract resize(cols: number, rows: number): void;
169 public abstract destroy(): void;
170 public abstract kill(signal?: string): void;
172 public abstract get process(): string;
173 public abstract get master(): Socket;
174 public abstract get slave(): Socket;
176 protected _close(): void {
177 this._socket.writable = false;
178 this._socket.readable = false;
179 this.write = () => {};
181 this._writable = false;
182 this._readable = false;
185 protected _parseEnv(env: {[key: string]: string}): string[] {
186 const keys = Object.keys(env || {});
189 for (let i = 0; i < keys.length; i++) {
190 pairs.push(keys[i] + '=' + env[keys[i]]);