From 60f18ede39e428867fbfe6df8c536fc2fcf79947 Mon Sep 17 00:00:00 2001 From: isaacs Date: Tue, 29 Jan 2013 17:50:44 -0800 Subject: [PATCH] readline: treat bare \r as a line ending Fixes #3305 --- lib/readline.js | 30 ++++++++++++++++++++++---- test/simple/test-readline-interface.js | 30 ++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/lib/readline.js b/lib/readline.js index d87d3a331e8..748f7dea418 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -49,6 +49,8 @@ function Interface(input, output, completer, terminal) { return new Interface(input, output, completer, terminal); } + this._sawReturn = false; + EventEmitter.call(this); if (arguments.length === 1) { @@ -292,18 +294,27 @@ Interface.prototype.write = function(d, key) { this.terminal ? this._ttyWrite(d, key) : this._normalWrite(d); }; +// \r\n, \n, or \r followed by something other than \n +var lineEnding = /\r?\n|\r(?!\n)/; Interface.prototype._normalWrite = function(b) { if (b === undefined) { return; } var string = this._decoder.write(b); + if (this._sawReturn) { + string = string.replace(/^\n/, ''); + this._sawReturn = false; + } + if (this._line_buffer) { string = this._line_buffer + string; this._line_buffer = null; } - if (string.indexOf('\n') !== -1) { + if (lineEnding.test(string)) { + this._sawReturn = /\r$/.test(string); + // got one or more newlines; process into "line" events - var lines = string.split(/\r?\n/); + var lines = string.split(lineEnding); // either '' or (concievably) the unfinished portion of the next line string = lines.pop(); this._line_buffer = string; @@ -733,11 +744,23 @@ Interface.prototype._ttyWrite = function(s, key) { } else { /* No modifier keys used */ + // \r bookkeeping is only relevant if a \n comes right after. + if (this._sawReturn && key.name !== 'enter') + this._sawReturn = false; + switch (key.name) { - case 'enter': + case 'return': // carriage return, i.e. \r + this._sawReturn = true; this._line(); break; + case 'enter': + if (this._sawReturn) + this._sawReturn = false + else + this._line(); + break; + case 'backspace': this._deleteLeft(); break; @@ -758,7 +781,6 @@ Interface.prototype._ttyWrite = function(s, key) { this._moveCursor(+1); break; - case 'return': // carriage return, i.e. \r case 'home': this._moveCursor(-Infinity); break; diff --git a/test/simple/test-readline-interface.js b/test/simple/test-readline-interface.js index 856f5964e8d..fc20d1d2129 100644 --- a/test/simple/test-readline-interface.js +++ b/test/simple/test-readline-interface.js @@ -126,6 +126,36 @@ FakeInput.prototype.end = function() {}; assert.equal(callCount, expectedLines.length - 1); rli.close(); + // \r\n should emit one line event when split across multiple writes. + fi = new FakeInput(); + rli = new readline.Interface({ input: fi, output: fi, terminal: terminal }); + expectedLines = ['foo', 'bar', 'baz', 'bat']; + callCount = 0; + rli.on('line', function(line) { + assert.equal(line, expectedLines[callCount]); + callCount++; + }); + expectedLines.forEach(function(line) { + fi.emit('data', line + '\r'); + fi.emit('data', '\n'); + }); + assert.equal(callCount, expectedLines.length); + rli.close(); + + // \r should behave like \n when alone + fi = new FakeInput(); + rli = new readline.Interface({ input: fi, output: fi, terminal: true }); + expectedLines = ['foo', 'bar', 'baz', 'bat']; + callCount = 0; + rli.on('line', function(line) { + assert.equal(line, expectedLines[callCount]); + callCount++; + }); + fi.emit('data', expectedLines.join('\r')); + assert.equal(callCount, expectedLines.length - 1); + rli.close(); + + // sending a multi-byte utf8 char over multiple writes var buf = Buffer('☮', 'utf8'); fi = new FakeInput();