diff --git a/index.js b/index.js index 076d717..612e9ac 100644 --- a/index.js +++ b/index.js @@ -11,6 +11,7 @@ const Readline = module.exports = exports = class Readline extends Readable { super() this._prompt = opts.prompt || '> ' + this._crlfDelay = opts.crlfDelay ? Math.max(100, opts.crlfDelay) : 100 this._oninput = this._oninput.bind(this) this._onkey = this._onkey.bind(this) @@ -18,12 +19,14 @@ const Readline = module.exports = exports = class Readline extends Readable { this._decoder = new KeyDecoder().on('data', this._onkey) this._history = new History() + this._sawReturn = 0 + this.input = opts.input.on('data', this._oninput) this.output = opts.output this.line = '' this.cursor = 0 - this.on('data', this._online).setEncoding('utf8') + this.on('data', this._ondata).setEncoding('utf8') } prompt () { @@ -48,19 +51,40 @@ const Readline = module.exports = exports = class Readline extends Readable { } clearLine () { + const line = this.line this.write(constants.EOL) this.line = '' this.cursor = 0 + return line } _oninput (data) { this._decoder.write(data) } - _online (line) { + _ondata (line) { this.emit('line', line) // For Node.js compatibility } + _online (linefeed) { + if (linefeed) { + if (this._sawReturn > 0 && Date.now() - this._sawReturn <= this._crlfDelay) return + + this._sawReturn = 0 + } else { + this._sawReturn = Date.now() + } + + const line = this.clearLine() + + const remember = line.trim() !== '' + if (remember && line !== this._history.get(0)) this._history.unshift(line) + + this.push(line) + + if (remember) this.emit('history', this._history.entries) + } + _onkey (key) { if (key.name === 'up') return this._onup() if (key.name === 'down') return this._ondown() @@ -82,16 +106,8 @@ const Readline = module.exports = exports = class Readline extends Readable { case 'backspace': return this._onbackspace() - case 'linefeed': - case 'return': { - const line = this.line - if (line.trim() === '') return - if (line !== this._history.get(0)) this._history.unshift(line) - this.push(line) - this.emit('history', this._history.entries) - this.clearLine() - return - } + case 'return': return this._online(false /* linefeed */) + case 'linefeed': return this._online(true /* linefeed */) case 'right': return this._onright() case 'left': return this._onleft() diff --git a/test.js b/test.js index 8d99cc3..db23b14 100644 --- a/test.js +++ b/test.js @@ -58,3 +58,21 @@ test('supports \\r\\n as single line event', (t) => { input.write('hello world') input.write('\r\n') }) + +test('emit empty line on return', (t) => { + t.plan(2) + + const input = new PassThrough() + const rl = new Readline({ input }) + + rl + .on('data', (line) => { + t.is(line, '') + rl.close() + }) + .on('close', () => { + t.pass('closed') + }) + + input.write('\r') +})