Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix input prompt repeating value when value is longer than terminal width. Closes #214 #239

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions lib/prompts/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ var _ = require("lodash");
var chalk = require("chalk");
var ansiRegex = require("ansi-regex");
var readline = require("readline");
var cliWidth = require("cli-width");
var utils = require("../utils/utils");
var Choices = require("../objects/choices");
var tty = require("../utils/tty");
Expand Down Expand Up @@ -96,7 +95,7 @@ Prompt.prototype.throwParamError = function( name ) {
*/

Prompt.prototype.error = function( error ) {
readline.moveCursor( this.rl.output, -cliWidth(), 0 );
readline.moveCursor( this.rl.output, -utils.cliWidth(), 0 );
readline.clearLine( this.rl.output, 0 );

var errMsg = chalk.red(">> ") +
Expand All @@ -114,7 +113,7 @@ Prompt.prototype.error = function( error ) {
*/

Prompt.prototype.hint = function( hint ) {
readline.moveCursor( this.rl.output, -cliWidth(), 0 );
readline.moveCursor( this.rl.output, -utils.cliWidth(), 0 );
readline.clearLine( this.rl.output, 0 );

if ( hint.length ) {
Expand Down
16 changes: 13 additions & 3 deletions lib/prompts/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* `input` type prompt
*/

var os = require("os");
var _ = require("lodash");
var util = require("util");
var chalk = require("chalk");
Expand Down Expand Up @@ -82,7 +83,7 @@ Prompt.prototype.onEnd = function( state ) {
this.status = "answered";

// Re-render prompt
this.clean(1).render();
this.clean( this.lines() ).render();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SBoudrias Do you have any insight on how to get this function (Prompt.prototype.onEnd) called in the unit tests? Currently only onKeypress seems to be called there, even when emitting a line event.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm not sure I understand this question. Mind clarifying?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SBoudrias If you take a look at my latest changes in the PR, I was able to unit test Prompt.prototype.onKeypress() by using this.rl.emit(), however calling this.rl.emit() in the unit tests doesn't call Prompt.prototype.onEnd() (it does call it when not mocking readline), so I wonder if you know a way to make Prompt.prototype.onEnd() get called as well, to unit test that function as well.


// Render answer
this.write( chalk.cyan(filteredValue) + "\n" );
Expand All @@ -100,5 +101,14 @@ Prompt.prototype.onError = function( state ) {
*/

Prompt.prototype.onKeypress = function() {
this.clean().render().write( this.rl.line );
};
var lines = this.lines();

// Windows duplicates the line only when the
// first character wraps to another line
if ( os.platform() === "win32" && this.previousLines === lines + 1 ) {
this.clean( 1 ).render();
}

this.previousLines = lines;
this.clean( this.previousLines - 1 ).render().write( this.rl.line );
};
47 changes: 45 additions & 2 deletions lib/utils/tty.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,55 @@
* TTY mixin helpers
*/

var os = require("os");
var _ = require("lodash");
var readline = require("readline");
var cliWidth = require("cli-width");
var unicodeLength = require("unicode-length");
var utils = require("./utils");

var tty = module.exports;

/**
* Get the number of lines the current line occupies
* @return {Number} The number of lines
*/

tty.lines = function() {
var terminalWidth = utils.cliWidth();

if ( terminalWidth === 0 ) {

// Preserve original behaviour
return 1;
}

var value = this.rl.line;

if ( !value ) {
if ( this.rl.history ) {
value = this.rl.history[0];
} else {
value = this.opt.default;
}
}

var lineLength = unicodeLength.get( this.rl._prompt + value );

// Windows seems to wraps the line one character
// before the window width for some reason
if ( os.platform() === "win32" ) {
lineLength += 1;
}

var result = lineLength / terminalWidth;

if ( result % 1 === 0 ) {
return result;
} else {
return Math.floor( result ) + 1;
}
};


/**
* Remove the prompt from screen
Expand All @@ -21,7 +64,7 @@ tty.clean = function( extra ) {
var len = this.height + extra;

while ( len-- ) {
readline.moveCursor(this.rl.output, -cliWidth(), 0);
readline.moveCursor(this.rl.output, -utils.cliWidth(), 0);
readline.clearLine(this.rl.output, 0);
if ( len ) readline.moveCursor(this.rl.output, 0, -1);
}
Expand Down
12 changes: 12 additions & 0 deletions lib/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ var _ = require("lodash");
var chalk = require("chalk");
var rx = require("rx");
var figures = require("figures");
var cliWidth = require("cli-width");


/**
Expand Down Expand Up @@ -121,3 +122,14 @@ utils.writeMessage = function ( prompt, message ) {
}
prompt.write( message );
};


/**
* Helper for getting the terminal width
* Wraps cli-width for stub purposes
* @return {Number} - The terminal width
*/

utils.cliWidth = function() {
return cliWidth();
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"lodash": "^3.3.1",
"readline2": "^0.1.1",
"rx": "^2.4.3",
"through": "^2.3.6"
"through": "^2.3.6",
"unicode-length": "^1.0.0"
},
"devDependencies": {
"chai": "^2.1.2",
Expand Down
1 change: 1 addition & 0 deletions test/helpers/readline.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var stub = {
pause : sinon.stub().returns(stub),
resume : sinon.stub().returns(stub),
_getCursorPos : sinon.stub().returns(stub),
_prompt : sinon.stub().returns(stub),
output : {
end : sinon.stub().returns(stub),
mute : sinon.stub().returns(stub),
Expand Down
68 changes: 68 additions & 0 deletions test/specs/prompts/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var _ = require("lodash");
var ReadlineStub = require("../../helpers/readline");
var fixtures = require("../../helpers/fixtures");

var utils = require("../../../lib/utils/utils");
var Input = require("../../../lib/prompts/input");


Expand Down Expand Up @@ -53,4 +54,71 @@ describe("`input` prompt", function() {
this.rl.emit("line", "");
});

describe("given a terminal width", function() {

beforeEach(function() {
this.cliWidthStub = sinon.stub( utils, "cliWidth" );
this.cliWidthStub.returns(20);
});

afterEach(function() {
this.cliWidthStub.restore();
});

it("should clean short lines appropriately", function( done ) {
var prompt = new Input( this.fixture, this.rl );
var cleanSpy = sinon.spy( prompt, "clean" );
prompt.run(function() {
expect( cleanSpy.callCount ).to.equal(1);
expect( cleanSpy.calledWith(1) ).to.be.true;
done();
}.bind(this));

this.rl.line = "hello";
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason, I have to assign this.rl.line directly, as emitting a line event doesn't assign the actual line to neither this.rl.line or this.rl.history, where tty expects them.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is correct as we use a mocked readline. So there's no usual Readline behavior during testing.

this.rl.emit( "line", this.rl.line );
});

it("should clean wrapped long lines", function( done ) {
var prompt = new Input( this.fixture, this.rl );
var cleanSpy = sinon.spy( prompt, "clean" );
prompt.run(function() {
expect( cleanSpy.callCount ).to.equal(1);
expect( cleanSpy.calledWith(3) ).to.be.true;
done();
}.bind(this));

this.rl.line = "asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf";
this.rl.emit( "line", this.rl.line );
});

});

describe("given no terminal width", function() {

beforeEach(function() {
this.cliWidthStub = sinon.stub( utils, "cliWidth" );

// Return the default value
this.cliWidthStub.returns(0);
});

afterEach(function() {
this.cliWidthStub.restore();
});

it("should clean one line even if the input line is long", function( done ) {
var prompt = new Input( this.fixture, this.rl );
var cleanSpy = sinon.spy( prompt, "clean" );
prompt.run(function() {
expect( cleanSpy.callCount ).to.equal(1);
expect( cleanSpy.calledWith(1) ).to.be.true;
done();
}.bind(this));

this.rl.line = "asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf";
this.rl.emit( "line", this.rl.line );
});

});

});