Browse Source

readline: strip ctrl chars for prompt width calc

Use regular expression to strip vt ansi escape codes from display when
calulating prompt display width and cursor position

Fixes #3860 and #5628.
v0.11.3-release
Krzysztof Chrapka 12 years ago
committed by Ben Noordhuis
parent
commit
ffcd8b94c2
  1. 23
      lib/readline.js
  2. 10
      test/simple/test-readline-interface.js

23
lib/readline.js

@ -557,6 +557,7 @@ Interface.prototype._getDisplayPos = function(str) {
var offset = 0; var offset = 0;
var col = this.columns; var col = this.columns;
var code; var code;
str = stripVTControlCharacters(str);
for (var i = 0, len = str.length; i < len; i++) { for (var i = 0, len = str.length; i < len; i++) {
code = codePointAt(str, i); code = codePointAt(str, i);
if (code >= 0x10000) { // surrogates if (code >= 0x10000) { // surrogates
@ -581,7 +582,7 @@ Interface.prototype._getDisplayPos = function(str) {
Interface.prototype._getCursorPos = function() { Interface.prototype._getCursorPos = function() {
var columns = this.columns; var columns = this.columns;
var strBeforeCursor = this._prompt + this.line.substring(0, this.cursor); var strBeforeCursor = this._prompt + this.line.substring(0, this.cursor);
var dispPos = this._getDisplayPos(strBeforeCursor); var dispPos = this._getDisplayPos(stripVTControlCharacters(strBeforeCursor));
var cols = dispPos.cols; var cols = dispPos.cols;
var rows = dispPos.rows; var rows = dispPos.rows;
// If the cursor is on a full-width character which steps over the line, // If the cursor is on a full-width character which steps over the line,
@ -921,9 +922,11 @@ exports.emitKeypressEvents = emitKeypressEvents;
*/ */
// Regexes used for ansi escape code splitting // Regexes used for ansi escape code splitting
var metaKeyCodeRe = /^(?:\x1b)([a-zA-Z0-9])$/; var metaKeyCodeReAnywhere = /(?:\x1b)([a-zA-Z0-9])/;
var functionKeyCodeRe = var metaKeyCodeRe = new RegExp('^' + metaKeyCodeReAnywhere.source + '$');
/^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/; var functionKeyCodeReAnywhere =
/(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/;
var functionKeyCodeRe = new RegExp('^' + functionKeyCodeReAnywhere.source);
function emitKey(stream, s) { function emitKey(stream, s) {
var ch, var ch,
@ -1207,6 +1210,7 @@ exports.clearScreenDown = clearScreenDown;
function getStringWidth(str) { function getStringWidth(str) {
var width = 0; var width = 0;
str = stripVTControlCharacters(str);
for (var i = 0, len = str.length; i < len; i++) { for (var i = 0, len = str.length; i < len; i++) {
var code = codePointAt(str, i); var code = codePointAt(str, i);
if (code >= 0x10000) { // surrogates if (code >= 0x10000) { // surrogates
@ -1289,3 +1293,14 @@ function codePointAt(str, index) {
return code; return code;
} }
exports.codePointAt = codePointAt; exports.codePointAt = codePointAt;
/**
* Tries to remove all VT control characters. Use to estimate displayed
* string width. May be buggy due to not running a real state machine
*/
function stripVTControlCharacters(str) {
str = str.replace(new RegExp(functionKeyCodeReAnywhere.source, 'g'), '');
return str.replace(new RegExp(metaKeyCodeReAnywhere.source, 'g'), '');
}
exports.stripVTControlCharacters = stripVTControlCharacters;

10
test/simple/test-readline-interface.js

@ -192,6 +192,16 @@ FakeInput.prototype.end = function() {};
assert.equal(readline.getStringWidth('안녕하세요'), 10); assert.equal(readline.getStringWidth('안녕하세요'), 10);
assert.equal(readline.getStringWidth('A\ud83c\ude00BC'), 5); // surrogate assert.equal(readline.getStringWidth('A\ud83c\ude00BC'), 5); // surrogate
// check if vt control chars are stripped
assert.equal(readline.stripVTControlCharacters('\u001b[31m> \u001b[39m'), '> ');
assert.equal(readline.stripVTControlCharacters('\u001b[31m> \u001b[39m> '), '> > ');
assert.equal(readline.stripVTControlCharacters('\u001b[31m\u001b[39m'), '');
assert.equal(readline.stripVTControlCharacters('> '), '> ');
assert.equal(readline.getStringWidth('\u001b[31m> \u001b[39m'), 2);
assert.equal(readline.getStringWidth('\u001b[31m> \u001b[39m> '), 4);
assert.equal(readline.getStringWidth('\u001b[31m\u001b[39m'), 0);
assert.equal(readline.getStringWidth('> '), 2);
assert.deepEqual(fi.listeners('end'), []); assert.deepEqual(fi.listeners('end'), []);
assert.deepEqual(fi.listeners(terminal ? 'keypress' : 'data'), []); assert.deepEqual(fi.listeners(terminal ? 'keypress' : 'data'), []);
}); });

Loading…
Cancel
Save