This turned out to be surprisingly difficult to find an answer for; I wanted to, in a node terminal program, wait for and return a single keypress.
The solution worked out to be to set process.stdin
into raw mode and listen for a data
event:
function getKeyPress() {
return new Promise((resolve, _reject) => {
process.stdin.setRawMode(true);
process.stdin.resume(); // needed if previously paused
process.stdin.once('data', (key) => {
process.stdin.pause(); // needed so that app doesn't hang
process.stdin.setRawMode(false);
resolve(key.toString('utf-8'));
});
});
}
One caveat is that this eats control codes: if you press ^C, the Promise resolves with '\u03'
.
Another worry is that if stdin
is a pipe, this will fail because there is no setRawMode
method, but getting a keypress from a pipe is silly so you’ll want to check for that before using this technique; process.stdin.isTTY
is your friend, or just check for the existence of process.stdin.setRawMode
.
A cool bonus though: at least on a mobile device, entering an emoji is read as a single keypress. Your terminal font might not display the emoji (I got a bunch of missing glyph markers in testing), but it’ll try, and it will be there in the string.
Edit 2024-09-30: With thanks to https://fosstodon.org/@rauschma, updated the function to be callable more than once. Thanks!