diff options
Diffstat (limited to 'src/js/node')
-rw-r--r-- | src/js/node/events.js | 2 | ||||
-rw-r--r-- | src/js/node/readline.js | 16 | ||||
-rw-r--r-- | src/js/node/tty.js | 289 |
3 files changed, 298 insertions, 9 deletions
diff --git a/src/js/node/events.js b/src/js/node/events.js index da7cfd57c..87c7af01b 100644 --- a/src/js/node/events.js +++ b/src/js/node/events.js @@ -37,6 +37,8 @@ EventEmitterPrototype.setMaxListeners = function setMaxListeners(n) { return this; }; +EventEmitterPrototype.constructor = EventEmitter; + EventEmitterPrototype.getMaxListeners = function getMaxListeners() { return this._maxListeners ?? defaultMaxListeners; }; diff --git a/src/js/node/readline.js b/src/js/node/readline.js index 0811fb95f..681cbba62 100644 --- a/src/js/node/readline.js +++ b/src/js/node/readline.js @@ -1604,15 +1604,13 @@ var _Interface = class Interface extends InterfaceConstructor { return this[kPrompt]; } - [kSetRawMode](mode) { - var input = this.input; - var { setRawMode, wasInRawMode } = input; - - // TODO: Make this work, for now just stub this and print debug - debug("setRawMode", mode, "set!"); - // if (typeof setRawMode === "function") { - // setRawMode(mode); - // } + [kSetRawMode](flag) { + const mode = flag ? 1 : 0; + const wasInRawMode = this.input.isRaw; + + if (typeof this.input.setRawMode === "function") { + this.input.setRawMode(mode); + } return wasInRawMode; } diff --git a/src/js/node/tty.js b/src/js/node/tty.js new file mode 100644 index 000000000..09010e8fc --- /dev/null +++ b/src/js/node/tty.js @@ -0,0 +1,289 @@ +const { ttySetMode, isatty, getWindowSize: _getWindowSize } = $lazy("tty"); + +function ReadStream(fd) { + if (!(this instanceof ReadStream)) return new ReadStream(fd); + if (fd >> 0 !== fd || fd < 0) throw new RangeError("fd must be a positive integer"); + + const stream = require("node:fs").ReadStream.call(this, `/dev/fd/${fd}`); + + stream.isRaw = false; + stream.isTTY = isatty(stream.fd); + + return stream; +} + +Object.defineProperty(ReadStream, "prototype", { + get() { + const Real = require("node:fs").ReadStream.prototype; + + Object.defineProperty(ReadStream, "prototype", { value: Real }); + ReadStream.prototype.setRawMode = function (flag) { + const mode = flag ? 1 : 0; + const err = ttySetMode(this.fd, mode); + if (err) { + this.emit("error", new Error("setRawMode failed with errno:", err)); + return this; + } + this.isRaw = flag; + return this; + }; + + return Real; + }, + enumerable: true, + configurable: true, +}); + +let OSRelease; + +const COLORS_2 = 1; +const COLORS_16 = 4; +const COLORS_256 = 8; +const COLORS_16m = 24; + +// Some entries were taken from `dircolors` +// (https://linux.die.net/man/1/dircolors). The corresponding terminals might +// support more than 16 colors, but this was not tested for. +// +// Copyright (C) 1996-2016 Free Software Foundation, Inc. Copying and +// distribution of this file, with or without modification, are permitted +// provided the copyright notice and this notice are preserved. +const TERM_ENVS = { + "eterm": COLORS_16, + "cons25": COLORS_16, + "console": COLORS_16, + "cygwin": COLORS_16, + "dtterm": COLORS_16, + "gnome": COLORS_16, + "hurd": COLORS_16, + "jfbterm": COLORS_16, + "konsole": COLORS_16, + "kterm": COLORS_16, + "mlterm": COLORS_16, + "mosh": COLORS_16m, + "putty": COLORS_16, + "st": COLORS_16, + // https://github.com/da-x/rxvt-unicode/tree/v9.22-with-24bit-color + "rxvt-unicode-24bit": COLORS_16m, + // https://gist.github.com/XVilka/8346728#gistcomment-2823421 + "terminator": COLORS_16m, +}; + +const TERM_ENVS_REG_EXP = [/ansi/, /color/, /linux/, /^con[0-9]*x[0-9]/, /^rxvt/, /^screen/, /^xterm/, /^vt100/]; + +let warned = false; +function warnOnDeactivatedColors(env) { + if (warned) return; + let name = ""; + if (env.NODE_DISABLE_COLORS !== undefined) name = "NODE_DISABLE_COLORS"; + if (env.NO_COLOR !== undefined) { + if (name !== "") { + name += "' and '"; + } + name += "NO_COLOR"; + } + + if (name !== "") { + process.emitWarning(`The '${name}' env is ignored due to the 'FORCE_COLOR' env being set.`, "Warning"); + warned = true; + } +} + +function WriteStream(fd) { + if (!(this instanceof WriteStream)) return new WriteStream(fd); + if (fd >> 0 !== fd || fd < 0) throw new RangeError("fd must be a positive integer"); + + const stream = require("node:fs").WriteStream.call(this, `/dev/fd/${fd}`); + + stream.columns = undefined; + stream.rows = undefined; + stream.isTTY = isatty(stream.fd); + + if (stream.isTTY) { + const windowSizeArray = [0, 0]; + if (_getWindowSize(fd, windowSizeArray) === true) { + stream.columns = windowSizeArray[0]; + stream.rows = windowSizeArray[1]; + } + } + + return stream; +} + +Object.defineProperty(WriteStream, "prototype", { + get() { + const Real = require("node:fs").WriteStream.prototype; + Object.defineProperty(WriteStream, "prototype", { value: Real }); + + WriteStream.prototype._refreshSize = function () { + const oldCols = this.columns; + const oldRows = this.rows; + const windowSizeArray = [0, 0]; + if (_getWindowSize(this.fd, windowSizeArray) === true) { + if (oldCols !== windowSizeArray[0] || oldRows !== windowSizeArray[1]) { + this.columns = windowSizeArray[0]; + this.rows = windowSizeArray[1]; + this.emit("resize"); + } + } + }; + + var readline = undefined; + WriteStream.prototype.clearLine = function (dir, cb) { + return (readline ??= require("node:readline")).clearLine(this, dir, cb); + }; + + WriteStream.prototype.clearScreenDown = function (cb) { + return (readline ??= require("node:readline")).clearScreenDown(this, cb); + }; + + WriteStream.prototype.cursorTo = function (x, y, cb) { + return (readline ??= require("node:readline")).cursorTo(this, x, y, cb); + }; + + // The `getColorDepth` API got inspired by multiple sources such as + // https://github.com/chalk/supports-color, + // https://github.com/isaacs/color-support. + WriteStream.prototype.getColorDepth = function (env = process.env) { + // Use level 0-3 to support the same levels as `chalk` does. This is done for + // consistency throughout the ecosystem. + if (env.FORCE_COLOR !== undefined) { + switch (env.FORCE_COLOR) { + case "": + case "1": + case "true": + warnOnDeactivatedColors(env); + return COLORS_16; + case "2": + warnOnDeactivatedColors(env); + return COLORS_256; + case "3": + warnOnDeactivatedColors(env); + return COLORS_16m; + default: + return COLORS_2; + } + } + + if ( + env.NODE_DISABLE_COLORS !== undefined || + // See https://no-color.org/ + env.NO_COLOR !== undefined || + // The "dumb" special terminal, as defined by terminfo, doesn't support + // ANSI color control codes. + // See https://invisible-island.net/ncurses/terminfo.ti.html#toc-_Specials + env.TERM === "dumb" + ) { + return COLORS_2; + } + + if (process.platform === "win32") { + // Lazy load for startup performance. + if (OSRelease === undefined) { + const { release } = require("node:os"); + OSRelease = StringPrototypeSplit(release(), "."); + } + // Windows 10 build 10586 is the first Windows release that supports 256 + // colors. Windows 10 build 14931 is the first release that supports + // 16m/TrueColor. + if (+OSRelease[0] >= 10) { + const build = +OSRelease[2]; + if (build >= 14931) return COLORS_16m; + if (build >= 10586) return COLORS_256; + } + + return COLORS_16; + } + + if (env.TMUX) { + return COLORS_256; + } + + if (env.CI) { + if ( + ["APPVEYOR", "BUILDKITE", "CIRCLECI", "DRONE", "GITHUB_ACTIONS", "GITLAB_CI", "TRAVIS"].some( + sign => sign in env, + ) || + env.CI_NAME === "codeship" + ) { + return COLORS_256; + } + return COLORS_2; + } + + if ("TEAMCITY_VERSION" in env) { + return RegExpPrototypeExec(/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/, env.TEAMCITY_VERSION) !== null + ? COLORS_16 + : COLORS_2; + } + + switch (env.TERM_PROGRAM) { + case "iTerm.app": + if (!env.TERM_PROGRAM_VERSION || RegExpPrototypeExec(/^[0-2]\./, env.TERM_PROGRAM_VERSION) !== null) { + return COLORS_256; + } + return COLORS_16m; + case "HyperTerm": + case "MacTerm": + return COLORS_16m; + case "Apple_Terminal": + return COLORS_256; + } + + if (env.COLORTERM === "truecolor" || env.COLORTERM === "24bit") { + return COLORS_16m; + } + + if (env.TERM) { + if (RegExpPrototypeExec(/^xterm-256/, env.TERM) !== null) { + return COLORS_256; + } + + const termEnv = StringPrototypeToLowerCase(env.TERM); + + if (TERM_ENVS[termEnv]) { + return TERM_ENVS[termEnv]; + } + if (ArrayPrototypeSome(TERM_ENVS_REG_EXP, term => RegExpPrototypeExec(term, termEnv) !== null)) { + return COLORS_16; + } + } + // Move 16 color COLORTERM below 16m and 256 + if (env.COLORTERM) { + return COLORS_16; + } + return COLORS_2; + }; + + WriteStream.prototype.getWindowSize = function () { + return [this.columns, this.rows]; + }; + + WriteStream.prototype.hasColors = function (count, env) { + if (env === undefined && (count === undefined || (typeof count === "object" && count !== null))) { + env = count; + count = 16; + } else { + validateInteger(count, "count", 2); + } + + return count <= 2 ** this.getColorDepth(env); + }; + + WriteStream.prototype.moveCursor = function (dx, dy, cb) { + return (readline ??= require("node:readline")).moveCursor(this, dx, dy, cb); + }; + + return Real; + }, + enumerable: true, + configurable: true, +}); + +var validateInteger = (value, name, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) => { + if (typeof value !== "number") throw new ERR_INVALID_ARG_TYPE(name, "number", value); + if (!Number.isInteger(value)) throw new ERR_OUT_OF_RANGE(name, "an integer", value); + if (value < min || value > max) throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); +}; + +export default { ReadStream, WriteStream, isatty }; |