From ffcd8b94c2111dcc72425ce0fbc48826ca8ba9ac Mon Sep 17 00:00:00 2001 From: Krzysztof Chrapka Date: Tue, 4 Jun 2013 17:01:14 +0200 Subject: [PATCH] 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. --- lib/readline.js | 23 +++++++++++++++++++---- test/simple/test-readline-interface.js | 10 ++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/lib/readline.js b/lib/readline.js index af025b8ef8..79b886946c 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -557,6 +557,7 @@ Interface.prototype._getDisplayPos = function(str) { var offset = 0; var col = this.columns; var code; + str = stripVTControlCharacters(str); for (var i = 0, len = str.length; i < len; i++) { code = codePointAt(str, i); if (code >= 0x10000) { // surrogates @@ -581,7 +582,7 @@ Interface.prototype._getDisplayPos = function(str) { Interface.prototype._getCursorPos = function() { var columns = this.columns; 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 rows = dispPos.rows; // 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 -var metaKeyCodeRe = /^(?:\x1b)([a-zA-Z0-9])$/; -var functionKeyCodeRe = - /^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/; +var metaKeyCodeReAnywhere = /(?:\x1b)([a-zA-Z0-9])/; +var metaKeyCodeRe = new RegExp('^' + metaKeyCodeReAnywhere.source + '$'); +var functionKeyCodeReAnywhere = + /(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/; +var functionKeyCodeRe = new RegExp('^' + functionKeyCodeReAnywhere.source); function emitKey(stream, s) { var ch, @@ -1207,6 +1210,7 @@ exports.clearScreenDown = clearScreenDown; function getStringWidth(str) { var width = 0; + str = stripVTControlCharacters(str); for (var i = 0, len = str.length; i < len; i++) { var code = codePointAt(str, i); if (code >= 0x10000) { // surrogates @@ -1289,3 +1293,14 @@ function codePointAt(str, index) { return code; } 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; diff --git a/test/simple/test-readline-interface.js b/test/simple/test-readline-interface.js index d0bc494c23..8b7c9a08bd 100644 --- a/test/simple/test-readline-interface.js +++ b/test/simple/test-readline-interface.js @@ -192,6 +192,16 @@ FakeInput.prototype.end = function() {}; assert.equal(readline.getStringWidth('안녕하세요'), 10); 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(terminal ? 'keypress' : 'data'), []); });