diff --git a/prybar_assets/nodejs/input-sync.js b/prybar_assets/nodejs/input-sync.js index 7b0414f..0fb884f 100644 --- a/prybar_assets/nodejs/input-sync.js +++ b/prybar_assets/nodejs/input-sync.js @@ -1,34 +1,19 @@ -const { readSync, writeSync, openSync } = require("fs"); -const { isatty } = require("tty"); +const { readSync, writeSync, openSync } = require('fs'); +const { isatty } = require('tty'); +const { Readable } = require('stream'); +const { createInterface } = require('readline'); + const buf = Buffer.alloc(1); const isTTY = isatty(process.stdin.fd); /** - * The escape (excluding \x1b) to move the cursur right one. - * - * This is what the right arrow key translates to in raw mode. - */ -const cursorRight = "[C"; -/** - * The escape (excluding \x1b) to move the cursur left one. - * - * This is what the left arrow key translates to in raw mode. - */ -const cursorLeft = "[D"; - -/** - * The ASCII character sent when the tty is in raw mode and backspace is pressed. - */ -const del = "\x7f"; - -/** - * The ASCII characcter sent when the tty is in raw mode and Ctrl+C is pressed. + * The ASCII character sent when the tty is in raw mode and Ctrl+C is pressed. */ -const endOfText = "\x03"; +const endOfText = '\x03'; /** * The ASCII character sent when the tty is in raw mode and Ctrl+D is pressed. */ -const endOfTransmission = "\x04"; +const endOfTransmission = '\x04'; /** * Reads a single byte from stdin to buf. @@ -47,8 +32,37 @@ function readByteSync() { const stdinFd = isTTY ? // We can't just use process.stdin.fd here since node has some getter shenanigans // which cause sync reads to throw - openSync("/dev/tty", "r") - : openSync("/dev/stdin", "r"); + openSync('/dev/tty', 'r') + : openSync('/dev/stdin', 'r'); + +class SyncReadable extends Readable { + constructor(fd) { + super(); + + this.fd = fd; + } + + _read() {} + + readNext() { + readByteSync(); + + this.push( + // copy the buffer to be safe + Buffer.concat([buf]) + ); + } +} + +const rd = new SyncReadable(stdinFd); +// prime reader +rd.read(); + +const rl = createInterface({ + input: rd, + output: process.stdout, + terminal: isTTY, +}); /** * Writes output to stdout. @@ -97,39 +111,6 @@ function ensureRawMode(cb) { return ret; } -/** - * Handles ANSI escapes from stdin. - * - * @return {string | -1 | 1} String if the escape isn't a left or right arrow . - * Otherwise, -1 on left arrow and 1 on right arrow - */ -function handleArrowKey() { - if (!readByteSync()) { - return "^"; - } - - let str = buf.toString("binary"); - - if (str !== "[") { - return `^${str}`; - } - - if (!readByteSync()) { - return `^${str}`; - } - - str += buf.toString("binary"); - - switch (str) { - case cursorRight: - return 1; - case cursorLeft: - return -1; - default: - return `^${str}`; - } -} - /** * Checks to see if the input character is what we get in raw mode for * Ctrl+C or Ctrl+D, and if so sends the proc SIGINT> @@ -142,57 +123,6 @@ function checkForSigs(char) { } } -/** - * Writes a string at an index of another string, appending as needed. - * - * @param {string} str The base string - * @param {string} other The new string which is being written - * @param {number} index The index at which the new string should start at. - * @return {string} str with other written at index. - */ -function insertAt(str, other, index) { - return [str.slice(0, index) + other + str.slice(index), index + other.length]; -} - -/** - * The ANSI escape code used to clear the contents of the current line to the right - * of the cursor. - */ -const escapeClearLineRight = "\x1b[K"; - -/** - * The escape used to move the cursor to a specific position in-line. - * - * @param {number} columnNum The position (starting at 1) in the current line which the cursor should be moved to. - * - */ -function escapeMoveCursorToColumn(columnNum) { - return `\x1b[${columnNum}G`; -} - -/** - * Sets the current line to our promt + string w/ the cursor at the right index. - * - * @param {string} prompt The question's prompt - * @param {string} current The current string (what the user has input so far) - * @param {number} index The index that - */ -function displayPromptAndStr(prompt, current, index) { - writeTTYOutput( - // reset cursor position - "\r" + - // clear the rest of the line - // EL (Erase in Line ): in this case, as no number is speciifed, - // erases everything to the right of the cursor. - escapeClearLineRight + - // write the prompt - prompt + - // write the string - current + - escapeMoveCursorToColumn(prompt.length + index + 1) - ); -} - /** * Synchronously reads from stdin until `\n` or `\r` * @@ -200,57 +130,15 @@ function displayPromptAndStr(prompt, current, index) { * @return {string} The input read (excluding newlines) */ function question(prompt) { - return ensureRawMode(() => { - let str = ""; - let index = 0; + let result = null; - if (!isTTY) { - writeOutput(prompt); - } - - for (;;) { - displayPromptAndStr(prompt, str, index); - const didRead = readByteSync(); - - if (!didRead) { - return str; - } - - const char = buf.toString("binary"); - checkForSigs(char); - - if (char === "\n" || char === "\r") { - writeTTYOutput("\r\n"); - - return str; - } else if (isTTY && char === "\x1b") { - const ret = handleArrowKey(); + rl.question(prompt, (d) => { + result = d; + }); - // if ret is a number, its the difference for the index - if (typeof ret === "number") { - // Only move the cursor if it will be in a valid position. - const newIndex = index + ret; - // the index can be equal to the strs length, if that's the case we're appending to the string. - if (newIndex >= 0 && newIndex <= str.length) { - index = newIndex; - } + while (result == null) rd.readNext(); - // otherwise, the escape wasn't a left or right arrow key, - // meaning we got an escaped version of the code. - } else { - [str, index] = insertAt(str, ret, index); - } - } else if (isTTY && char === del) { - if (index > 0) { - index--; - // remove the character at the old index - str = str.slice(0, index) + str.slice(index + 1); - } - } else { - [str, index] = insertAt(str, char, index); - } - } - }); + return result; } /** @@ -263,22 +151,22 @@ function question(prompt) { */ function keyInYNStrict(prompt) { return ensureRawMode(() => { - writeOutput(`${prompt == null ? "Are you sure?" : prompt} [y/n]: `); + writeOutput(`${prompt == null ? 'Are you sure?' : prompt} [y/n]: `); for (;;) { const didRead = readByteSync(); if (!didRead) { - throw new Error("Unexpected EOF / end of input. Expected y/n."); + throw new Error('Unexpected EOF / end of input. Expected y/n.'); } - const char = buf.toString("binary"); + const char = buf.toString('binary'); checkForSigs(char); if (char.match(/[yn]/i)) { writeTTYOutput(`${char}\r\n`); - return char === "y" || char === "Y"; + return char === 'y' || char === 'Y'; } } });