nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
90 lines (73 loc) • 2.71 kB
JavaScript
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/readline/emitKeypressEvents.js
import {
charLengthAt,
CSI,
emitKeys,
} from "nstdlib/lib/internal/readline/utils";
import { kSawKeyPress } from "nstdlib/lib/internal/readline/interface";
import { clearTimeout, setTimeout } from "nstdlib/lib/timers";
import { StringDecoder } from "nstdlib/lib/string_decoder";
const { kEscape } = CSI;
const KEYPRESS_DECODER = Symbol("keypress-decoder");
const ESCAPE_DECODER = Symbol("escape-decoder");
// GNU readline library - keyseq-timeout is 500ms (default)
const ESCAPE_CODE_TIMEOUT = 500;
/**
* accepts a readable Stream instance and makes it emit "keypress" events
*/
function emitKeypressEvents(stream, iface = {}) {
if (stream[KEYPRESS_DECODER]) return;
stream[KEYPRESS_DECODER] = new StringDecoder("utf8");
stream[ESCAPE_DECODER] = emitKeys(stream);
stream[ESCAPE_DECODER].next();
const triggerEscape = () => stream[ESCAPE_DECODER].next("");
const { escapeCodeTimeout = ESCAPE_CODE_TIMEOUT } = iface;
let timeoutId;
function onData(input) {
if (stream.listenerCount("keypress") > 0) {
const string = stream[KEYPRESS_DECODER].write(input);
if (string) {
clearTimeout(timeoutId);
// This supports characters of length 2.
iface[kSawKeyPress] = charLengthAt(string, 0) === string.length;
iface.isCompletionEnabled = false;
let length = 0;
for (const character of new SafeStringIterator(string)) {
length += character.length;
if (length === string.length) {
iface.isCompletionEnabled = true;
}
try {
stream[ESCAPE_DECODER].next(character);
// Escape letter at the tail position
if (length === string.length && character === kEscape) {
timeoutId = setTimeout(triggerEscape, escapeCodeTimeout);
}
} catch (err) {
// If the generator throws (it could happen in the `keypress`
// event), we need to restart it.
stream[ESCAPE_DECODER] = emitKeys(stream);
stream[ESCAPE_DECODER].next();
throw err;
}
}
}
} else {
// Nobody's watching anyway
stream.removeListener("data", onData);
stream.on("newListener", onNewListener);
}
}
function onNewListener(event) {
if (event === "keypress") {
stream.on("data", onData);
stream.removeListener("newListener", onNewListener);
}
}
if (stream.listenerCount("keypress") > 0) {
stream.on("data", onData);
} else {
stream.on("newListener", onNewListener);
}
}
export default emitKeypressEvents;