From e28f77cbad1dea617aeda18513b61e6ecce8e294 Mon Sep 17 00:00:00 2001 From: Nathan Friedly Date: Fri, 6 Apr 2012 11:41:59 -0700 Subject: [PATCH] readline: buffer data to only emit 'line' on '\n' In "terminal: false" mode. (And fire it multiple times if multiple lines arrive at once.) This is necessary because the Windows telnet client sends every single keystroke as it's typed. See: http://stackoverflow.com/questions/9962197/node-js-readline-not-waiting-for-a-full-line-on-socket-connections Closes #3059. --- lib/readline.js | 19 ++++- test/simple/test-readline-interface.js | 106 +++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 test/simple/test-readline-interface.js diff --git a/lib/readline.js b/lib/readline.js index d910d09999..7da3ce75d6 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -264,12 +264,23 @@ Interface.prototype.write = function(d, key) { this.terminal ? this._ttyWrite(d, key) : this._normalWrite(d, key); }; +// telnet on windows sends every single keystroke seperately, so we need +// to buffer them and only fire the 'line' event when a \n is sent +Interface.prototype._line_buffer = ''; Interface.prototype._normalWrite = function(b) { - // Very simple implementation right now. Should try to break on - // new lines. - if (b !== undefined) - this._onLine(b.toString()); + if (b === undefined) { + return; + } + this._line_buffer += b.toString(); + if (this._line_buffer.indexOf('\n') !== -1) { + var lines = this._line_buffer.split('\n'); + // either '' or (concievably) the unfinished portion of the next line + this._line_buffer = lines.pop(); + lines.forEach(function(line) { + this._onLine(line + '\n'); + }, this); + } }; Interface.prototype._insertString = function(c) { diff --git a/test/simple/test-readline-interface.js b/test/simple/test-readline-interface.js new file mode 100644 index 0000000000..fc8d4f2fe4 --- /dev/null +++ b/test/simple/test-readline-interface.js @@ -0,0 +1,106 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + + + +var assert = require('assert'); +var readline = require('readline'); +var EventEmitter = require('events').EventEmitter; +var inherits = require('util').inherits; + +function FakeInput() { + EventEmitter.call(this); +} +inherits(FakeInput, EventEmitter); +FakeInput.prototype.resume = function() {}; + +var fi; +var rli; +var called; + +// sending a full line +fi = new FakeInput(); +rli = new readline.Interface(fi, {}); +called = false; +rli.on('line', function(line) { + called = true; + assert.equal(line, 'asdf\n'); +}); +fi.emit('data', 'asdf\n'); +assert.ok(called); + +// sending a blank line +fi = new FakeInput(); +rli = new readline.Interface(fi, {}); +called = false; +rli.on('line', function(line) { + called = true; + assert.equal(line, '\n'); +}); +fi.emit('data', '\n'); +assert.ok(called); + +// sending a single character with no newline +fi = new FakeInput(); +rli = new readline.Interface(fi, {}); +called = false; +rli.on('line', function(line) { + called = true; +}); +fi.emit('data', 'a'); +assert.ok(!called); + +// sending a single character with no newline and then a newline +fi = new FakeInput(); +rli = new readline.Interface(fi, {}); +called = false; +rli.on('line', function(line) { + called = true; + assert.equal(line, 'a\n'); +}); +fi.emit('data', 'a'); +assert.ok(!called); +fi.emit('data', '\n'); +assert.ok(called); + +// sending multiple newlines at once +fi = new FakeInput(); +rli = new readline.Interface(fi, {}); +var expectedLines = ['foo\n', 'bar\n', 'baz\n']; +var callCount = 0; +rli.on('line', function(line) { + assert.equal(line, expectedLines[callCount]); + callCount++; +}); +fi.emit('data', expectedLines.join('')); +assert.equal(callCount, expectedLines.length); + +// sending multiple newlines at once that does not end with a new line +fi = new FakeInput(); +rli = new readline.Interface(fi, {}); +var expectedLines = ['foo\n', 'bar\n', 'baz\n', 'bat']; +var callCount = 0; +rli.on('line', function(line) { + assert.equal(line, expectedLines[callCount]); + callCount++; +}); +fi.emit('data', expectedLines.join('')); +assert.equal(callCount, expectedLines.length - 1);