"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnixTerminal = void 0;
/**
 * Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
 * Copyright (c) 2016, Daniel Imms (MIT License).
 * Copyright (c) 2018, Microsoft Corporation (MIT License).
 */
var fs = require("fs");
var path = require("path");
var tty = require("tty");
var terminal_1 = require("./terminal");
var utils_1 = require("./utils");
var native = utils_1.loadNativeModule('pty');
var pty = native.module;
var helperPath = native.dir + '/spawn-helper';
helperPath = path.resolve(__dirname, helperPath);
helperPath = helperPath.replace('app.asar', 'app.asar.unpacked');
helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked');
var DEFAULT_FILE = 'sh';
var DEFAULT_NAME = 'xterm';
var DESTROY_SOCKET_TIMEOUT_MS = 200;
var UnixTerminal = /** @class */ (function (_super) {
    __extends(UnixTerminal, _super);
    function UnixTerminal(file, args, opt) {
        var _a, _b;
        var _this = _super.call(this, opt) || this;
        _this._boundClose = false;
        _this._emittedClose = false;
        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 || terminal_1.DEFAULT_COLS;
        _this._rows = opt.rows || terminal_1.DEFAULT_ROWS;
        var uid = (_a = opt.uid) !== null && _a !== void 0 ? _a : -1;
        var gid = (_b = opt.gid) !== null && _b !== void 0 ? _b : -1;
        var env = utils_1.assign({}, opt.env);
        if (opt.env === process.env) {
            _this._sanitizeEnv(env);
        }
        var cwd = opt.cwd || process.cwd();
        env.PWD = cwd;
        var name = opt.name || env.TERM || DEFAULT_NAME;
        env.TERM = name;
        var parsedEnv = _this._parseEnv(env);
        var encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding);
        var onexit = function (code, signal) {
            // 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.
                var timeout_1 = setTimeout(function () {
                    timeout_1 = null;
                    // Destroying the socket now will cause the close event to fire
                    _this._socket.destroy();
                }, DESTROY_SOCKET_TIMEOUT_MS);
                _this.once('close', function () {
                    if (timeout_1 !== null) {
                        clearTimeout(timeout_1);
                    }
                    _this.emit('exit', code, signal);
                });
                return;
            }
            _this.emit('exit', code, signal);
        };
        // fork
        var term = pty.fork(file, args, parsedEnv, cwd, _this._cols, _this._rows, uid, gid, (encoding === 'utf8'), helperPath, onexit);
        _this._socket = new tty.ReadStream(term.fd);
        if (encoding !== null) {
            _this._socket.setEncoding(encoding);
        }
        _this._writeStream = new CustomWriteStream(term.fd, (encoding || undefined));
        // setup
        _this._socket.on('error', function (err) {
            // 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', function () {
            if (_this._emittedClose) {
                return;
            }
            _this._emittedClose = true;
            _this._close();
            _this.emit('close');
        });
        _this._forwardEvents();
        return _this;
    }
    Object.defineProperty(UnixTerminal.prototype, "master", {
        get: function () { return this._master; },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(UnixTerminal.prototype, "slave", {
        get: function () { return this._slave; },
        enumerable: false,
        configurable: true
    });
    UnixTerminal.prototype._write = function (data) {
        this._writeStream.write(data);
    };
    Object.defineProperty(UnixTerminal.prototype, "fd", {
        /* Accessors */
        get: function () { return this._fd; },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(UnixTerminal.prototype, "ptsName", {
        get: function () { return this._pty; },
        enumerable: false,
        configurable: true
    });
    /**
     * openpty
     */
    UnixTerminal.open = function (opt) {
        var self = Object.create(UnixTerminal.prototype);
        opt = opt || {};
        if (arguments.length > 1) {
            opt = {
                cols: arguments[1],
                rows: arguments[2]
            };
        }
        var cols = opt.cols || terminal_1.DEFAULT_COLS;
        var rows = opt.rows || terminal_1.DEFAULT_ROWS;
        var encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding);
        // open
        var term = pty.open(cols, rows);
        self._master = new tty.ReadStream(term.master);
        if (encoding !== null) {
            self._master.setEncoding(encoding);
        }
        self._master.resume();
        self._slave = new tty.ReadStream(term.slave);
        if (encoding !== null) {
            self._slave.setEncoding(encoding);
        }
        self._slave.resume();
        self._socket = self._master;
        self._pid = -1;
        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', function (err) {
            self._close();
            if (self.listeners('error').length < 2) {
                throw err;
            }
        });
        self._socket.on('close', function () {
            self._close();
        });
        return self;
    };
    UnixTerminal.prototype.destroy = function () {
        var _this = this;
        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', function () {
            _this.kill('SIGHUP');
        });
        this._socket.destroy();
        this._writeStream.dispose();
    };
    UnixTerminal.prototype.kill = function (signal) {
        try {
            process.kill(this.pid, signal || 'SIGHUP');
        }
        catch (e) { /* swallow */ }
    };
    Object.defineProperty(UnixTerminal.prototype, "process", {
        /**
         * Gets the name of the process.
         */
        get: function () {
            if (process.platform === 'darwin') {
                var title = pty.process(this._fd);
                return (title !== 'kernel_task') ? title : this._file;
            }
            return pty.process(this._fd, this._pty) || this._file;
        },
        enumerable: false,
        configurable: true
    });
    /**
     * TTY
     */
    UnixTerminal.prototype.resize = function (cols, rows) {
        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;
    };
    UnixTerminal.prototype.clear = function () {
    };
    UnixTerminal.prototype._sanitizeEnv = function (env) {
        // 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'];
    };
    return UnixTerminal;
}(terminal_1.Terminal));
exports.UnixTerminal = UnixTerminal;
/**
 * A custom write stream that writes directly to a file descriptor with proper
 * handling of backpressure and errors. This avoids some event loop exhaustion
 * issues that can occur when using the standard APIs in Node.
 */
var CustomWriteStream = /** @class */ (function () {
    function CustomWriteStream(_fd, _encoding) {
        this._fd = _fd;
        this._encoding = _encoding;
        this._writeQueue = [];
    }
    CustomWriteStream.prototype.dispose = function () {
        clearImmediate(this._writeImmediate);
        this._writeImmediate = undefined;
    };
    CustomWriteStream.prototype.write = function (data) {
        // Writes are put in a queue and processed asynchronously in order to handle
        // backpressure from the kernel buffer.
        var buffer = typeof data === 'string'
            ? Buffer.from(data, this._encoding)
            : Buffer.from(data);
        if (buffer.byteLength !== 0) {
            this._writeQueue.push({ buffer: buffer, offset: 0 });
            if (this._writeQueue.length === 1) {
                this._processWriteQueue();
            }
        }
    };
    CustomWriteStream.prototype._processWriteQueue = function () {
        var _this = this;
        this._writeImmediate = undefined;
        if (this._writeQueue.length === 0) {
            return;
        }
        var task = this._writeQueue[0];
        // Write to the underlying file descriptor and handle it directly, rather
        // than using the `net.Socket`/`tty.WriteStream` wrappers which swallow and
        // mask errors like EAGAIN and can cause the thread to block indefinitely.
        fs.write(this._fd, task.buffer, task.offset, function (err, written) {
            if (err) {
                if ('code' in err && err.code === 'EAGAIN') {
                    // `setImmediate` is used to yield to the event loop and re-attempt
                    // the write later.
                    _this._writeImmediate = setImmediate(function () { return _this._processWriteQueue(); });
                }
                else {
                    // Stop processing immediately on unexpected error and log
                    _this._writeQueue.length = 0;
                    console.error('Unhandled pty write error', err);
                }
                return;
            }
            task.offset += written;
            if (task.offset >= task.buffer.byteLength) {
                _this._writeQueue.shift();
            }
            // Since there is more room in the kernel buffer, we can continue to write
            // until we hit EAGAIN or exhaust the queue.
            //
            // Note that old versions of bash, like v3.2 which ships in macOS, appears
            // to have a bug in its readline implementation that causes data
            // corruption when writes to the pty happens too quickly. Instead of
            // trying to workaround that we just accept it so that large pastes are as
            // fast as possible.
            // Context: https://github.com/microsoft/node-pty/issues/833
            _this._processWriteQueue();
        });
    };
    return CustomWriteStream;
}());
//# sourceMappingURL=unixTerminal.js.map