Is there a way to clear readline prompt line? - node.js

I've been looking around everywhere and I was still unable to find an answer.
I have an example program here which requests input with readline and then logs something after the input was entered.
Here is an extremely basic example:
const readline = require("readline");
const rl = readline.createInterface(process.stdin, process.stdout);
rl.question("What is your name? ", function(res) {
console.log("Hey, your name is", res);
rl.close();
});
What I want to do is completely hide What is your name? (name) after enter was pressed and only show Hey, your name is .... It seems like such a basic thing but after hours of research I failed to find a solution.
I have tried process.stdout.clearLine() but since enter makes a new line, it clears that new line instead which keeps the input. I have also tried appending \r to the end of the input but since enter creates a new line, the same happens as with clearLine.

This can be done by using the readline.moveCursor and readline.clearScreenDown methods.
const readline = require("readline");
const rl = readline.createInterface(process.stdin, process.stdout);
console.log("Hello there (dont delete this)");
rl.question("What is your name? ", function(res) {
// moves up 3 lines (question + answer + enter new line)
readline.moveCursor(process.stdout,0,-3);
readline.clearScreenDown(process.stdout);
console.log("Hey, your name is", res);
rl.close();
});
Example REPL
You may need to perform some math if the question or response contains new lines.

Related

Node.js: readline inside replace()

I have a string with the alphabet and certain letters are in parenthesis. It means these letters should be uppercase. But the user has to be asked first if he wants it.
The problem is, I have to call readline inside a loop and this doesn't work.
const readline = require('readline');
const text = 'a(b)cdefg(h)ijklmnopqrst(u)vwxyz';
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
text.replace(/\((\w)\)/g, function (match, p1) {
let answer;
rl.question(`Do you want to change ${p1}? [yes/no] `, input => {
answer = input;
});
return answer === "yes" ? p1.toUpperCase() : p1;
});
console.log("Result:", text);
It just prints out Do you want to change b? [yes/no] Result: a(b)cdefg(h)ijklmnopqrst(u)vwxyz.
How can this be solved?
As Nick has explained in the comments the main issue of your code it that it does not handle the async readline operation correctly - here's how I would do this:
use a promisifed version of readline to prevent nested callbacks
use a while loop to step through all regex matches and await the user input in order to convert the chars to upper case or not
Something like (still needs error handling, but should give you an idea):
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
function question(query) {
return new Promise(resolve => {
readline.question(query, resolve);
})
}
const specialCharsRegex = /\((\w)\)/g;
const text = 'a(b)cdefg(h)ijklmnopqrst(u)vwxyz';
(async () => {
let result = text;
let currMatch;
while ((currMatch = specialCharsRegex.exec(text)) !== null) {
const convertInput = await question(`Do you want to change ${currMatch[1]}? [yes/no] `);
result = result.replace(currMatch[0], v => {
return convertInput === "yes" ? currMatch[1].toUpperCase() : currMatch[1];
});
}
console.log("Result: ", result);
readline.close();
})();
The main issue with your code is that you're trying to do something asynchronous in a callback that is expected to be synchronous - that is, you're trying to read user input in the .replace() callback, but reading user input doesn't happen immediately, so the callback to .question() occurs sometime after your question has been asked and your replace callback has terminated/finished. Making rl.question() synchronous would fix this (Node.js synchronous prompt) and would allow you to prompt against the exact words you're replacing.
If you want to keep things asynchronous, you could first match the letters you want to replace using .matchAll(), that way you'll have the text/groups, and the indexes of the characters you want to change. Then, you can use a loop to iterate the matches, and prompt the user whether they want to change the group. For each group, you can update the text accordingly by updating the text at the specified index. You could use replace, but if your text has duplicates eg: "a(b)c(b)e", and you said "no" for the first (b) but yes for the second (b), then you'll either need to replace both occurrences, or the first match. By using the index, you can specify exactly which group to change:
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin, //or fileStream
output: process.stdout
});
const question = q => new Promise(res => rl.question(q, res));
let text = 'a(b)cdefg(h)ijklmnopqrst(u)vwxyz';
(async () => {
const matches = text.matchAll(/\((\w)\)/g);
for(const {0: match, 1: p1, index} of matches) {
const ans = await question(`Do you want to change ${p1}? [yes/no] `);
if(ans === "yes")
text = text.slice(0, index) + match.toUpperCase() + text.slice(index + match.length);
}
console.log(text);
})();

Node.js listen keyboard and read the line interactively?

I'm looking for a way to implement something like fzf, in node.js. For Example, I have a SQLite database, I would like to "SELECT" each time user typed something from keyboard input. If user typed "python" python rows will be logged but if typed "pythonn" (just type "n" after "python"), the node program will retrieve "pythonn" so you probably might not get "python" rows.
I searched google, stackoverflow and tested some programs:
var stdin = process.openStdin();
stdin.on('data', function(chunk) { console.log("Got chunk: " + chunk); });
The above program can only read a "character" not line and also you need to type enter key to fire the function.
const readline = require('readline');
readline.emitKeypressEvents(process.stdin);
process.stdin.setRawMode(true);
process.stdin.on('keypress', (str, key) => {
if (key.ctrl && key.name === 'c') {
process.exit();
} else {
console.log(`You pressed the "${str}" key`);
console.log();
console.log(key);
console.log();
}
});
console.log('Press any key...');
For this one, you don't need to type enter key but this is also for a character reading.
So, how can I do something like that, listen keyboard and read the line interactively? Thanks.

How to write a file with fs.writeFileSync in a list

Im trying to make my first "AI"
that store the words that i use with a "chat bot" everytime that i open
like
if i use "hello" and later i use "Heya" the chatbot will write these words on a json file
And use these words randomly to greet me when i open the program
(At this moment i just trying to write the words on a list, the bot greeting me with these words i already don't created)
heres the code that i tryied:
const fs = require('fs');
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Hello! ', (answer) => {
// TODO: Log the answer in a database
console.log(`The ${answer} word has stored :)`);
fs.writeFileSync(__dirname + '/startkeywords.json', JSON.stringify(answer, null, 4));
console.log('created at' + __dirname);
rl.close();
});
but, when i execute the program and say something,its writes,but its not on a list
that just writes, so i'm trying to write the file on a list like this:
list example
help pls
If you want to store your "keywords" as a valid json file, you might need to read file, add data and write again:
const fs = require('fs')
const startKeywordsFile = 'startkeywords.json'
// create an empty json if not exists
if (!fs.existsSync(startKeywordsFile)) {
fs.writeFileSync(startKeywordsFile, '[]')
}
let words = JSON.parse(fs.readFileSync(startKeywordsFile, 'utf8'))
function addWord(newWord) {
if (words.includes(newWord)) {
// already exists
return
}
words.push(newWord)
fs.writeFileSync(startKeywordsFile, JSON.stringify(words, null, 4))
}
addWord('hi')
addWord('hello')
startkeywords.json:
[
"hi",
"hello"
]
Keep in mind it might have performance issue with a large list. If you want to save your keywords as plain text (one word a line, not valid json), you can use fs.appendFileSync to append your file without having to rewrite the entire file everytime.

NodeJs keystrokes get printed twice in console [duplicate]

This question already has answers here:
Get user input through Node.js console
(6 answers)
Closed last month.
I'm new to NodeJs and i'm used to C# were we can use
Console.ReadLine();
I looked into 'readline' and the node prompt package, but it either outputs all the user input twice while entering or, with the 'terminal: false' option, does not allow us to use the backspace.
var stdin = process.openStdin();
stdin.addListener("data", function(d) {
console.log("your input: " + d.toString());
});
This is like you take input value
There is a readline, you can use it like this:
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('How are you today? ', (answer) => {
// TODO: Log the answer in a database
console.log(`Thank you for your valuable feedback: ${answer}`);
rl.close();
});
Documentation is available here.

user input with node.js

I have the following Node.js code that behaves strangely:
#!/usr/bin/env node
"use strict";
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function input(prompt) {
rl.question(prompt, function (x) {
rl.close();
console.log("debug: " + x);
return x;
});
}
function main() {
var n = input("Number: ");
// console.log("value: " + n); // problematic line
}
main();
I want to mimic Python's raw_input, i.e. reading a line from the user. After showing the prompt, the program should be blocked until the user presses Enter.
If the "problematic line" is in comments, it works, the program is waiting for input. However, if this line is not in comments, then the program doesn't wait for input and n becomes undefined. Why? How to write a function that returns the user input?
That's because you are expecting the execution of input wait until return is called, which is not how this will work. The problematic line is indeed the previous one. First, input does not return anything, the return statement is the return of the question callback function, but then, you seem to misunderstand the execution flow, as we all have at some point (you'll get it pretty quick after some dead-ends like this one)
Your script is loaded
You declare and define rl, input and main
Main executes
You define n as the result of input
And here is where things start getting asynchronously funny
since question is asynchronous, its execution start but does not block the process
input returns undefined (while you're still waiting for the input)
you print that undefined
You write something on the input
question() finishes its execution and calls the callback (the function you gave as second parameter)
rl is closed
the callback function returns the line, and it is swallowed by the void (this is not technical terminology, just a metaphor)
You may want to do it like this:
#!/usr/bin/env node
"use strict";
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function input(prompt, callback) {
rl.question(prompt, function (x) {
rl.close();
callback(x);
});
}
function main() {
var n = input("Number: ", console.log);
}
main();
If you're new to javascript and node, you may find very useful to use learnyounode and the node code school path or even, if you have the time, the money and the opportunity, read Node.js, MongoDB, and AngularJS Web Development, by Brad Dayley.

Resources