0

I am designing a console app in Node.js using readline to control the cursor positions and get user input. Here is a library I have written for the same:

// ReadLine.js
const readline = require("readline");
const readSync = require("readline-sync");

const inStream = process.stdin;
const outStream = process.stdout;

class ReadLine {
    constructor() {
        this.io = readline.createInterface({input: inStream, output: outStream});
    }
    
    setEvent(event, callback) {
        this.io.on(event, callback);
    };
    
    exit() {
        this.io.close();
    }
    
    // Move Cursor to Row,Col Position
    moveTo(row, col) {
        readline.cursorTo(process.stdout, col, row);
        // this.io.write(`\x1b[${row};${col}H`);
    };
    
    clearRow(row) {
        this.moveTo(row, 0);
        this.io.write(`\x1b[0K`); // Clears from the cursor to the end of the row
    };
    
    // Write text at current cursor position
    write(msg) {
        this.io.write(msg);
    };
    
    // Write text at specific Row,Col Position
    writeAt(row, col, msg) {
        this.moveTo(row, col);
        this.write(msg);
    };
    
    // Ask a question, wait for an input
    ask(question, callback) {
        this.io.question(question, callback);
        // const answer = readSync.question(question);
        // callback(answer);
    };
}

module.exports = {ReadLine};

And here is a sample test for this library:

// ReadCli.js

console.clear();
const ReadLine = require("./ReadLine");
const io = new ReadLine.ReadLine();
io.setEvent("close", process.exit);

const Ask = () => {
    const choices = ["Print Fibonacci", "Exit"];
    let row = 0;
    const col = 3;
    choices.forEach(choice => io.writeAt(++row, col, `${row} ${choice}`));
    io.moveTo(++row, col);
    io.ask("Enter Your Choice: ", answer => {
        const lastChoice = choices.length;
        switch(Number.parseInt(answer)) {
            case lastChoice:
                io.clearRow(++row);
                io.writeAt(row, col, "Goodbye");
                io.exit();
                break;
            default:
                io.writeAt(++row, col, "Wrong Choice Entered");
        }
        Ask();
    });
};
Ask();

Although the code seems straightforward, the interface printing in the console is beyond my understanding. I cannot deduce how the cursor is moving around different readline methods. Here is a summary of my problems:

  1. With the current code, the choices are printed again even before the user has given any input, at the current position. Although the choices are re-printed on the same line of question, the cursor is positioned just after Enter Your Choice: <cursor_position>. I have no idea what's happening here. The choices should not be re-printed, and even after re-printing the cursor is not positioned at the end of printing, but at the end of the question:

enter image description here

  1. The question is positioned at the specified row, but not at the specified column. I checked this on some other tests, readline.question prints the question at the current cursor row, but it always prints from the first column, not from the current cursor column.

  2. If I uncomment this line

this.io.write(`\x1b[${row};${col}H`);

and comment this

// readline.cursorTo(process.stdout, col, row);

I got almost the desired output:

output wi

But the problem with readline.question still persists, it's still printing on the specific row, but not from the specified column, but from the first column only.

  1. None of the problems occur when I use readline-sync instead of readline:
const readSync = require("readline-sync");


// Ask a question, wait for an input
    ask(question, callback) {
        // this.io.question(question, callback);
        const answer = readSync.question(question);
        callback(answer);
    };

But I can't see the user input. Cursor doesn't move when user provide any input and input is not shown on screen:

enter image description here

enter image description here

So I need to understand what's happening with these two lines in the context of printing and cursor positioning

readline.cursorTo(process.stdout, col, row);
readline.question(question, callback);

Thanks and sorry for such a long question.

1 Answer 1

0

After some investigation into the source code this is what I have discovered:

  1. When you do an io.write without an endline character, the content will be stored by readline as it assumes you are still editing the same line. The subsequent io.question will trigger a prompt that pushes the content to the back and renders the message at the front of the line (I have no idea why). See Readline.line

  2. The io.question always moves the internal cursor to x=0, See kRefreshLine

I was going to suggest appending endline on all io.write but this causes the last line of the message to be cleared during rerender, you can either continue to use the terminal escape code or use io.write with endline and manually rerender the last line of message on rerender.

As for the question's column position, the only thing you can do is manually prepending the space in front of the question prompt.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.