Adding colors to terminal prompt results in large

2019-03-30 05:49发布

问题:

I'm working on a simple cli script and wanted to add some color to the following code:

rl.question('Enter destination path: ', function(answer) {
     // ...                                                                                                                                
});                                                                                                                                  
rl.write('/home/' + user + '/bin');

Which displays in the terminal:

Enter destination path: /home/jmcateer/bin_

But I wanted to add some color to the prompt I did the following:

rl.question('\u001b[1;36mEnter destination path:\u001b[0m ', function(answer) {

});                                                                                                                                  
rl.write('/home/' + user + '/bin');

And the command line prompt ended up displaying:

Enter destination path:                 /home/jmcateer/bin_

It works but there's a huge amount of white space I'd prefer weren't there. Does anyone have any ideas on how to deal with this?

Edit:

I can't delete the white space by backspacing through it... when I try to use the backspace key the white space jumps to the other end like so

Enter destination path:                 /home/jmcateer/bin_
Enter destination path: /home/jmcateer/bi                _
Enter destination path: /home/jmcateer/b                _
...
Enter destination path:                 _

At that point backspace has no effect.

回答1:

When you call rl.setPrompt(prompt, length) without its second argument, the internal _promptLength variable is set to the length of the prompt string; ANSI X3.64 escape sequences are not interpreted. The internal _getCursorPos method computes the cursor position from _promptLength][3]; as such, the inclusion of the escape sequences in the length results in the cursor being positioned further away than it should be.

To resolve this problem fully, Node's readline library should parse the ANSI escape sequences when setting _promptLength. To work around this problem, you can manually calculate the length of the prompt string without the escape sequences and pass that as the second argument to rl.setPrompt(prompt, length).



回答2:

I also ran into a similar issue. Basil Crow is correct in his excellent answer on the cause of the problem in that it is indeed the ANSI escape sequences that are causing the length glitch; however, Interface.setPrompt() is not just the problem—it is the solution!

It seems that this misreading of the length (something I artfully avoided while playing around in bash) affects the entire Interface object's writeout process, i.e. anything that calls Interface.setPrompt() in any capacity will be afflicted when the length parameter is left not specified.

In order to overcome this problem, you can do one of two things:


Redefine Interface.setPrompt() to always specify a length for prompt output so that methods like Interface.question() function properly again:

// I'm using the 'color' npm library for the sake of convenience. It is not required
// and you can use normal regex to strip color codes and what not.
var colors   = require('colors'),
    readline = require('readline');

var rl = readline.createInterface(process.stdin, process.stdout);

/* Overcome some bugs in the Nodejs readline implementation */
rl._setPrompt = rl.setPrompt;
rl.setPrompt = function(prompt, length)
{
    rl._setPrompt(prompt, length ? length : prompt.split(/[\r\n]/).pop().stripColors.length);
};

var str = '[' + '?'.green + '] Blackbeard walks under the black flag with a ____? ';

rl.question(str, function(answer)
{
    var answ = 'scallywag swagger';
    console.log(
        'You answered "' + ((answer == answ) ? answer.green : answer.red)
        + '". The correct answer is', '"' + answ.green + '".');
});


Redefine Interface.write() to use Interface.setPrompt() passing both the writeout string and the true length of the string to the setPrompt method:

var colors   = require('colors'),
    readline = require('readline');

var rl = readline.createInterface(process.stdin, process.stdout);

/* Overcome some bugs in the Nodejs readline implementation */
rl._write = rl.write; 
rl.write = function(d, key)
{
    // "key" functionality is lost, but if you care enough you can add it back
    rl.setPrompt(d, d.split(/[\r\n]/).pop().stripColors.length);
    rl.prompt(true);
};

var str = '[' + '?'.green + '] Blackbeard walks under the black flag with a ____? ';
rl.write(str);

rl.on('line', function(answer)
{
    var answ = 'scallywag swagger';
    console.log(
        'You answered "' + ((answer == answ) ? answer.green : answer.red)
        + '". The correct answer is', '"' + answ.green + '".');
    rl.prompt(true);
});


The results for both are the same:

Or you can do both. Or you can modify the readline.Interface.prototype directly (and have your fix applied globally) instead of the object instances themselves. Lots of options here.

Hope this helps someone!

EDIT—See also: https://github.com/joyent/node/issues/3860



回答3:

Sure, you'll want to modify your rl.write string with the CSI sequence n D where n is the number of characters to move your cursor back.

Here is a snippet to experiment with:

var rl = require('readline').createInterface({input: process.stdin, output: process.stdout});

rl.question('\u001b[1;36mEnter destination path: \u001b[0m', function(answer) {

});                                                                                               
rl.write('\u001b[11 D/home/jp/bin');

Notice the 11 and the D in the last line? The D stands for the number of characters to move back. 11 is obviously then the number of characters.

See this for all of the fun terminal codes: http://en.wikipedia.org/wiki/ANSI_escape_code