Skip to content

Commit 3c91a7a

Browse files
committed
readline: use a "string_decoder" to parse "keypress" events
While updating the readline test cases to test both "terimal: false" and "terminal: true" mode, it turned out that the test case testing utf8 chars being sent over multiple write() calls was failing. The solution is to use a string_decoder instance when parsing the "keypress" events.
1 parent e95e095 commit 3c91a7a

File tree

2 files changed

+97
-91
lines changed

2 files changed

+97
-91
lines changed

lib/readline.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -801,12 +801,14 @@ exports.Interface = Interface;
801801
*/
802802

803803
function emitKeypressEvents(stream) {
804-
if (stream._emitKeypress) return;
805-
stream._emitKeypress = true;
804+
if (stream._keypressDecoder) return;
805+
var StringDecoder = require('string_decoder').StringDecoder; // lazy load
806+
stream._keypressDecoder = new StringDecoder('utf8');
806807

807808
function onData(b) {
808809
if (stream.listeners('keypress').length > 0) {
809-
emitKey(stream, b);
810+
var r = stream._keypressDecoder.write(b);
811+
if (r) emitKey(stream, r);
810812
} else {
811813
// Nobody's watching anyway
812814
stream.removeListener('data', onData);

test/simple/test-readline-interface.js

Lines changed: 92 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -32,100 +32,104 @@ function FakeInput() {
3232
inherits(FakeInput, EventEmitter);
3333
FakeInput.prototype.resume = function() {};
3434
FakeInput.prototype.pause = function() {};
35+
FakeInput.prototype.write = function() {};
36+
FakeInput.prototype.end = function() {};
3537

36-
var fi;
37-
var rli;
38-
var called;
38+
[ true, false ].forEach(function(terminal) {
39+
var fi;
40+
var rli;
41+
var called;
3942

40-
// sending a full line
41-
fi = new FakeInput();
42-
rli = new readline.Interface(fi, {});
43-
called = false;
44-
rli.on('line', function(line) {
45-
called = true;
46-
assert.equal(line, 'asdf');
47-
});
48-
fi.emit('data', 'asdf\n');
49-
assert.ok(called);
43+
// sending a full line
44+
fi = new FakeInput();
45+
rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
46+
called = false;
47+
rli.on('line', function(line) {
48+
called = true;
49+
assert.equal(line, 'asdf');
50+
});
51+
fi.emit('data', 'asdf\n');
52+
assert.ok(called);
5053

51-
// sending a blank line
52-
fi = new FakeInput();
53-
rli = new readline.Interface(fi, {});
54-
called = false;
55-
rli.on('line', function(line) {
56-
called = true;
57-
assert.equal(line, '');
58-
});
59-
fi.emit('data', '\n');
60-
assert.ok(called);
54+
// sending a blank line
55+
fi = new FakeInput();
56+
rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
57+
called = false;
58+
rli.on('line', function(line) {
59+
called = true;
60+
assert.equal(line, '');
61+
});
62+
fi.emit('data', '\n');
63+
assert.ok(called);
6164

62-
// sending a single character with no newline
63-
fi = new FakeInput();
64-
rli = new readline.Interface(fi, {});
65-
called = false;
66-
rli.on('line', function(line) {
67-
called = true;
68-
});
69-
fi.emit('data', 'a');
70-
assert.ok(!called);
71-
rli.close();
65+
// sending a single character with no newline
66+
fi = new FakeInput();
67+
rli = new readline.Interface(fi, {});
68+
called = false;
69+
rli.on('line', function(line) {
70+
called = true;
71+
});
72+
fi.emit('data', 'a');
73+
assert.ok(!called);
74+
rli.close();
7275

73-
// sending a single character with no newline and then a newline
74-
fi = new FakeInput();
75-
rli = new readline.Interface(fi, {});
76-
called = false;
77-
rli.on('line', function(line) {
78-
called = true;
79-
assert.equal(line, 'a');
80-
});
81-
fi.emit('data', 'a');
82-
assert.ok(!called);
83-
fi.emit('data', '\n');
84-
assert.ok(called);
85-
rli.close();
76+
// sending a single character with no newline and then a newline
77+
fi = new FakeInput();
78+
rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
79+
called = false;
80+
rli.on('line', function(line) {
81+
called = true;
82+
assert.equal(line, 'a');
83+
});
84+
fi.emit('data', 'a');
85+
assert.ok(!called);
86+
fi.emit('data', '\n');
87+
assert.ok(called);
88+
rli.close();
8689

87-
// sending multiple newlines at once
88-
fi = new FakeInput();
89-
rli = new readline.Interface(fi, {});
90-
var expectedLines = ['foo\n', 'bar\n', 'baz\n'];
91-
var callCount = 0;
92-
rli.on('line', function(line) {
93-
assert.equal(line, expectedLines[callCount]);
94-
callCount++;
95-
});
96-
fi.emit('data', expectedLines.join('\n') + '\n');
97-
assert.equal(callCount, expectedLines.length);
98-
rli.close();
90+
// sending multiple newlines at once
91+
fi = new FakeInput();
92+
rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
93+
var expectedLines = ['foo', 'bar', 'baz'];
94+
var callCount = 0;
95+
rli.on('line', function(line) {
96+
assert.equal(line, expectedLines[callCount]);
97+
callCount++;
98+
});
99+
fi.emit('data', expectedLines.join('\n') + '\n');
100+
assert.equal(callCount, expectedLines.length);
101+
rli.close();
99102

100-
// sending multiple newlines at once that does not end with a new line
101-
fi = new FakeInput();
102-
rli = new readline.Interface(fi, {});
103-
var expectedLines = ['foo\n', 'bar\n', 'baz\n', 'bat'];
104-
var callCount = 0;
105-
rli.on('line', function(line) {
106-
assert.equal(line, expectedLines[callCount]);
107-
callCount++;
108-
});
109-
fi.emit('data', expectedLines.join('\n'));
110-
assert.equal(callCount, expectedLines.length - 1);
111-
rli.close();
103+
// sending multiple newlines at once that does not end with a new line
104+
fi = new FakeInput();
105+
rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
106+
expectedLines = ['foo', 'bar', 'baz', 'bat'];
107+
callCount = 0;
108+
rli.on('line', function(line) {
109+
assert.equal(line, expectedLines[callCount]);
110+
callCount++;
111+
});
112+
fi.emit('data', expectedLines.join('\n'));
113+
assert.equal(callCount, expectedLines.length - 1);
114+
rli.close();
112115

113-
// sending a multi-byte utf8 char over multiple writes
114-
var buf = Buffer('☮', 'utf8');
115-
fi = new FakeInput();
116-
rli = new readline.Interface(fi, {});
117-
callCount = 0;
118-
rli.on('line', function(line) {
119-
callCount++;
120-
assert.equal(line, buf.toString('utf8'));
121-
});
122-
[].forEach.call(buf, function(i) {
123-
fi.emit('data', Buffer([i]));
124-
});
125-
assert.equal(callCount, 0);
126-
fi.emit('data', '\n');
127-
assert.equal(callCount, 1);
128-
rli.close();
116+
// sending a multi-byte utf8 char over multiple writes
117+
var buf = Buffer('☮', 'utf8');
118+
fi = new FakeInput();
119+
rli = new readline.Interface({ input: fi, output: fi, terminal: terminal });
120+
callCount = 0;
121+
rli.on('line', function(line) {
122+
callCount++;
123+
assert.equal(line, buf.toString('utf8'));
124+
});
125+
[].forEach.call(buf, function(i) {
126+
fi.emit('data', Buffer([i]));
127+
});
128+
assert.equal(callCount, 0);
129+
fi.emit('data', '\n');
130+
assert.equal(callCount, 1);
131+
rli.close();
129132

130-
assert.deepEqual(fi.listeners('end'), []);
131-
assert.deepEqual(fi.listeners('data'), []);
133+
assert.deepEqual(fi.listeners('end'), []);
134+
assert.deepEqual(fi.listeners(terminal ? 'keypress' : 'data'), []);
135+
});

0 commit comments

Comments
 (0)