var readline = require("readline");
var iface = new readline.Interface({
input: process.stdin,
output: process.stdout,
prompt: ""
});
var i = 0;
var string = 'Loading';
var done = false;
// Once ctrl-c is hit, it sets done to true.
iface.once('SIGINT', () => { done = true; });
test();
function test() {
// Clear the line, move the cursor to the beginning and print loading again
readline.clearLine(process.stdout, 0);
readline.moveCursor(process.stdout, (-string.length - 4), 0);
iface.write(string);
// print i dots
for (var n = 0; n < i; n++) {
iface.write('.');
}
if (done) {
// Make sure there are 3 dots on the line
for (var n = 0; n < 3 - i; n++) {
iface.write('.');
}
// Show that program is done then close.
iface.write('done\n');
iface.close();
return;
} else {
i = i < 3 ? i + 1 : 0; // Increment i if i is less than 3, otherwise set i to 0
setTimeout(test, 1000);
}
}
Code Objective:
Make a loop animating the printing of three dots to indicate that the computer is responsive and the app is not hanging. Loading is completed when the user presses Ctrl-C. While the current code isn't the optimal way of doing things, it outputs like so:
Loading (clear line) Loading. (clear line) Loading.. (clear line) Loading... (clear line) Loading (clear line)...
Issue:
This code works pretty well for the first minute or so then it will suddenly print out all of the output history all at once. However, the program will still continue the animation as expected. After a certain amount of time, it will print out the output history again.
Here is the output to one of those instances:
LoadingLoading.Loading..Loading...LoadingLoading.Loading..Loading...LoadingLoading.Loading..
Loading...LoadingLoading.Loading..Loading...LoadingLoading.Loading..Loading...LoadingLoading.
Loading..Loading...LoadingLoading.Loading..Loading...LoadingLoading.Loading..Loading...Loading
Loading.Loading..Loading...LoadingLoading.Loading..Loading...LoadingLoading.Loading..Loading...
LoadingLoading.Loading..Loading...LoadingLoading.Loading..Loading...LoadingLoading.Loading..
Loading...LoadingLoading.Loading..Loading...LoadingLoading.Loading..Loading...LoadingLoading.
Loading..Loading...LoadingLoading
Workaround:
The issue was fixed when iface.write() is replaced with process.stdout.write(). This may have to do with how iface.write works. Unless you are doing something that requires iface.write(), it's probably best that you write directly to the stream object (IE. process.stdout.write()).
Can reproduce with the last Node.js 4, 6, 7, and 8 on Windows 7 x64 with cmd.exe.
The problem is that readline.clearLine() and readline.moveCursor() functions operate on a raw TTY stream sending VT100 escape sequences and thereadline.Interface knows nothing about it, these changes only happen on the screen while iface.line and iface.cursor just keep accumulating. When the total count of the characters that you've written via iface.write() becomes equal to or exceeds the count of columns in your terminal, the interface refreshes the line while creating a new blank one and all those characters become visible.
In this case using process.stdout.write() instead of iface.write() is a perfectly valid solution, I don't see anything in this snippet of code that requires readline.Interface functionality. (And yes, you are not obliged to use readline.Interface if you use some functions of the readline module itself; to the contrary, I'd say things like readline.clearLine() and readline.moveCursor() should have better been in the tty module.)
However, if the code snippet in this issue is purely synthetic while you encountered the issue in the situation when using readline.Interface is necessary, I'd suggest you another workaround:
18,19c18
< readline.clearLine(process.stdout, 0);
< readline.moveCursor(process.stdout, (-string.length - 4), 0);
---
> iface.write(null, { ctrl: true, name: 'u' });
This will send the "Ctrl+U" keys to the readline interface, effectively clearing the current line properly. Feel free to turn this issue into a feature-request for a new public method of readline.Interface (there is an undocumented iface.clearLine() already, but it doesn't do exactly what you need).
This issue has been inactive for sufficiently long that it seems like perhaps it should be closed. Feel free to re-open (or leave a comment requesting that it be re-opened) if you disagree. I'm just tidying up and not acting on a super-strong opinion or anything like that.
Most helpful comment
The problem is that
readline.clearLine()andreadline.moveCursor()functions operate on a raw TTY stream sending VT100 escape sequences and thereadline.Interfaceknows nothing about it, these changes only happen on the screen whileiface.lineandiface.cursorjust keep accumulating. When the total count of the characters that you've written viaiface.write()becomes equal to or exceeds the count of columns in your terminal, the interface refreshes the line while creating a new blank one and all those characters become visible.In this case using
process.stdout.write()instead ofiface.write()is a perfectly valid solution, I don't see anything in this snippet of code that requiresreadline.Interfacefunctionality. (And yes, you are not obliged to usereadline.Interfaceif you use some functions of thereadlinemodule itself; to the contrary, I'd say things likereadline.clearLine()andreadline.moveCursor()should have better been in thettymodule.)However, if the code snippet in this issue is purely synthetic while you encountered the issue in the situation when using
readline.Interfaceis necessary, I'd suggest you another workaround:This will send the "Ctrl+U" keys to the readline interface, effectively clearing the current line properly. Feel free to turn this issue into a feature-request for a new public method of
readline.Interface(there is an undocumentediface.clearLine()already, but it doesn't do exactly what you need).