X-Git-Url: https://git.josue.xyz/?p=VSoRC%2F.git;a=blobdiff_plain;f=node_modules%2Fnode-pty%2Fsrc%2FunixTerminal.ts;fp=node_modules%2Fnode-pty%2Fsrc%2FunixTerminal.ts;h=58e45677eee863128dabb0274ad0c7916890f222;hp=0000000000000000000000000000000000000000;hb=e79e4a5a87f3e84f7c1777f10a954453a69bf540;hpb=4339da12467b75fb8b6ca831f4bf0081c485ed2c diff --git a/node_modules/node-pty/src/unixTerminal.ts b/node_modules/node-pty/src/unixTerminal.ts new file mode 100644 index 0000000..58e4567 --- /dev/null +++ b/node_modules/node-pty/src/unixTerminal.ts @@ -0,0 +1,299 @@ +/** + * Copyright (c) 2012-2015, Christopher Jeffrey (MIT License) + * Copyright (c) 2016, Daniel Imms (MIT License). + * Copyright (c) 2018, Microsoft Corporation (MIT License). + */ +import * as net from 'net'; +import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal'; +import { IProcessEnv, IPtyForkOptions, IPtyOpenOptions } from './interfaces'; +import { ArgvOrCommandLine } from './types'; +import { assign } from './utils'; + +let pty: IUnixNative; +try { + pty = require('../build/Release/pty.node'); +} catch (outerError) { + try { + pty = require('../build/Debug/pty.node'); + } catch (innerError) { + console.error('innerError', innerError); + // Re-throw the exception from the Release require if the Debug require fails as well + throw outerError; + } +} + +const DEFAULT_FILE = 'sh'; +const DEFAULT_NAME = 'xterm'; +const DESTROY_SOCKET_TIMEOUT_MS = 200; + +export class UnixTerminal extends Terminal { + protected _fd: number; + protected _pty: string; + + protected _file: string; + protected _name: string; + + protected _readable: boolean; + protected _writable: boolean; + + private _boundClose: boolean; + private _emittedClose: boolean; + private _master: net.Socket; + private _slave: net.Socket; + + public get master(): net.Socket { return this._master; } + public get slave(): net.Socket { return this._slave; } + + constructor(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions) { + super(opt); + + if (typeof args === 'string') { + throw new Error('args as a string is not supported on unix.'); + } + + // Initialize arguments + args = args || []; + file = file || DEFAULT_FILE; + opt = opt || {}; + opt.env = opt.env || process.env; + + this._cols = opt.cols || DEFAULT_COLS; + this._rows = opt.rows || DEFAULT_ROWS; + const uid = opt.uid || -1; + const gid = opt.gid || -1; + const env = assign({}, opt.env); + + if (opt.env === process.env) { + this._sanitizeEnv(env); + } + + const cwd = opt.cwd || process.cwd(); + const name = opt.name || env.TERM || DEFAULT_NAME; + env.TERM = name; + const parsedEnv = this._parseEnv(env); + + const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding); + + const onexit = (code: number, signal: number) => { + // XXX Sometimes a data event is emitted after exit. Wait til socket is + // destroyed. + if (!this._emittedClose) { + if (this._boundClose) { + return; + } + this._boundClose = true; + // From macOS High Sierra 10.13.2 sometimes the socket never gets + // closed. A timeout is applied here to avoid the terminal never being + // destroyed when this occurs. + let timeout = setTimeout(() => { + timeout = null; + // Destroying the socket now will cause the close event to fire + this._socket.destroy(); + }, DESTROY_SOCKET_TIMEOUT_MS); + this.once('close', () => { + if (timeout !== null) { + clearTimeout(timeout); + } + this.emit('exit', code, signal); + }); + return; + } + this.emit('exit', code, signal); + }; + + // fork + const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), onexit); + + this._socket = new PipeSocket(term.fd); + if (encoding !== null) { + this._socket.setEncoding(encoding); + } + + // setup + this._socket.on('error', (err: any) => { + // NOTE: fs.ReadStream gets EAGAIN twice at first: + if (err.code) { + if (~err.code.indexOf('EAGAIN')) { + return; + } + } + + // close + this._close(); + // EIO on exit from fs.ReadStream: + if (!this._emittedClose) { + this._emittedClose = true; + this.emit('close'); + } + + // EIO, happens when someone closes our child process: the only process in + // the terminal. + // node < 0.6.14: errno 5 + // node >= 0.6.14: read EIO + if (err.code) { + if (~err.code.indexOf('errno 5') || ~err.code.indexOf('EIO')) { + return; + } + } + + // throw anything else + if (this.listeners('error').length < 2) { + throw err; + } + }); + + this._pid = term.pid; + this._fd = term.fd; + this._pty = term.pty; + + this._file = file; + this._name = name; + + this._readable = true; + this._writable = true; + + this._socket.on('close', () => { + if (this._emittedClose) { + return; + } + this._emittedClose = true; + this._close(); + this.emit('close'); + }); + + this._forwardEvents(); + } + + protected _write(data: string): void { + this._socket.write(data); + } + + /** + * openpty + */ + + public static open(opt: IPtyOpenOptions): UnixTerminal { + const self: UnixTerminal = Object.create(UnixTerminal.prototype); + opt = opt || {}; + + if (arguments.length > 1) { + opt = { + cols: arguments[1], + rows: arguments[2] + }; + } + + const cols = opt.cols || DEFAULT_COLS; + const rows = opt.rows || DEFAULT_ROWS; + const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding); + + // open + const term: IUnixOpenProcess = pty.open(cols, rows); + + self._master = new PipeSocket(term.master); + if (encoding !== null) { + self._master.setEncoding(encoding); + } + self._master.resume(); + + self._slave = new PipeSocket(term.slave); + if (encoding !== null) { + self._slave.setEncoding(encoding); + } + self._slave.resume(); + + self._socket = self._master; + self._pid = null; + self._fd = term.master; + self._pty = term.pty; + + self._file = process.argv[0] || 'node'; + self._name = process.env.TERM || ''; + + self._readable = true; + self._writable = true; + + self._socket.on('error', err => { + self._close(); + if (self.listeners('error').length < 2) { + throw err; + } + }); + + self._socket.on('close', () => { + self._close(); + }); + + return self; + } + + public destroy(): void { + this._close(); + + // Need to close the read stream so node stops reading a dead file + // descriptor. Then we can safely SIGHUP the shell. + this._socket.once('close', () => { + this.kill('SIGHUP'); + }); + + this._socket.destroy(); + } + + public kill(signal?: string): void { + try { + process.kill(this.pid, signal || 'SIGHUP'); + } catch (e) { /* swallow */ } + } + + /** + * Gets the name of the process. + */ + public get process(): string { + return pty.process(this._fd, this._pty) || this._file; + } + + /** + * TTY + */ + + public resize(cols: number, rows: number): void { + if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) { + throw new Error('resizing must be done using positive cols and rows'); + } + pty.resize(this._fd, cols, rows); + this._cols = cols; + this._rows = rows; + } + + private _sanitizeEnv(env: IProcessEnv): void { + // Make sure we didn't start our server from inside tmux. + delete env['TMUX']; + delete env['TMUX_PANE']; + + // Make sure we didn't start our server from inside screen. + // http://web.mit.edu/gnu/doc/html/screen_20.html + delete env['STY']; + delete env['WINDOW']; + + // Delete some variables that might confuse our terminal. + delete env['WINDOWID']; + delete env['TERMCAP']; + delete env['COLUMNS']; + delete env['LINES']; + } +} + +/** + * Wraps net.Socket to force the handle type "PIPE" by temporarily overwriting + * tty_wrap.guessHandleType. + * See: https://github.com/chjj/pty.js/issues/103 + */ +class PipeSocket extends net.Socket { + constructor(fd: number) { + const { Pipe, constants } = (process).binding('pipe_wrap'); // tslint:disable-line + // @types/node has fd as string? https://github.com/DefinitelyTyped/DefinitelyTyped/pull/18275 + const handle = new Pipe(constants.SOCKET); + handle.open(fd); + super({ handle }); + } +}