Skip to content

Commit

Permalink
fixup! readline: add Promise-based API
Browse files Browse the repository at this point in the history
  • Loading branch information
aduh95 committed Mar 30, 2021
1 parent 1f08545 commit 0861993
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 23 deletions.
22 changes: 8 additions & 14 deletions lib/readline.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,11 @@ Interface.prototype.question = function(query, options, cb) {

if (options.signal) {
if (options.signal.aborted) {
this[kQuestionCancel]();
return;
}
options.signal.addEventListener(
'abort',
() => {
this[kQuestionCancel]();
},
{ once: true }
);
options.signal.addEventListener('abort', () => {
this[kQuestionCancel]();
}, { once: true });
}
if (typeof cb === 'function') {
FunctionPrototypeCall(_Interface.prototype.question, this,
Expand Down Expand Up @@ -377,7 +372,8 @@ function _ttyWriteDumb(s, key) {

if (key.name === 'escape') return;

if (this[kSawReturnAt] && key.name !== 'enter') this[kSawReturnAt] = 0;
if (this[kSawReturnAt] && key.name !== 'enter')
this[kSawReturnAt] = 0;

if (key.ctrl) {
if (key.name === 'c') {
Expand All @@ -396,17 +392,15 @@ function _ttyWriteDumb(s, key) {
}

switch (key.name) {
case 'return': // Carriage return, i.e. \r
case 'return': // Carriage return, i.e. \r
this[kSawReturnAt] = DateNow();
this[kLine]();
break;

case 'enter':
// When key interval > crlfDelay
if (
this[kSawReturnAt] === 0 ||
DateNow() - this[kSawReturnAt] > this.crlfDelay
) {
if (this[kSawReturnAt] === 0 ||
DateNow() - this[kSawReturnAt] > this.crlfDelay) {
this[kLine]();
}
this[kSawReturnAt] = 0;
Expand Down
4 changes: 4 additions & 0 deletions lib/readline/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const {
} = require('internal/errors');

class Interface extends _Interface {
// eslint-disable-next-line no-useless-constructor
constructor(input, output, completer, terminal) {
super(input, output, completer, terminal);
}
question(query, options = {}) {
return new Promise((resolve, reject) => {
if (options.signal) {
Expand Down
17 changes: 16 additions & 1 deletion test/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,10 +383,25 @@ function _mustCallInner(fn, criteria = 1, field) {

mustCallChecks.push(context);

return function() {
const _return = function() { // eslint-disable-line func-style
context.actual++;
return fn.apply(this, arguments);
};
Object.defineProperties(_return, {
name: {
value: fn.name,
writable: false,
enumerable: false,
configurable: true,
},
length: {
value: fn.length,
writable: false,
enumerable: false,
configurable: true,
},
});
return _return;
}

function hasMultiLocalhost() {
Expand Down
116 changes: 116 additions & 0 deletions test/parallel/test-readline-promises-tab-complete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
'use strict';

// Flags: --expose-internals

const common = require('../common');
const readline = require('readline/promises');
const assert = require('assert');
const { EventEmitter } = require('events');
const { getStringWidth } = require('internal/util/inspect');

common.skipIfDumbTerminal();

// This test verifies that the tab completion supports unicode and the writes
// are limited to the minimum.
[
'あ',
'𐐷',
'🐕'
].forEach((char) => {
[true, false].forEach((lineBreak) => {
[
(line) => [
['First group', '',
`${char}${'a'.repeat(10)}`,
`${char}${'b'.repeat(10)}`,
char.repeat(11),
],
line
],

async (line) => [
['First group', '',
`${char}${'a'.repeat(10)}`,
`${char}${'b'.repeat(10)}`,
char.repeat(11),
],
line
],
].forEach((completer) => {

let output = '';
const width = getStringWidth(char) - 1;

class FakeInput extends EventEmitter {
columns = ((width + 1) * 10 + (lineBreak ? 0 : 10)) * 3

write = common.mustCall((data) => {
output += data;
}, 6)

resume() {}
pause() {}
end() {}
}

const fi = new FakeInput();
const rli = new readline.Interface({
input: fi,
output: fi,
terminal: true,
completer: common.mustCallAtLeast(completer),
});

const last = '\r\nFirst group\r\n\r\n' +
`${char}${'a'.repeat(10)}${' '.repeat(2 + width * 10)}` +
`${char}${'b'.repeat(10)}` +
(lineBreak ? '\r\n' : ' '.repeat(2 + width * 10)) +
`${char.repeat(11)}\r\n` +
`\r\n\u001b[1G\u001b[0J> ${char}\u001b[${4 + width}G`;

const expectations = [char, '', last];

rli.on('line', common.mustNotCall());
for (const character of `${char}\t\t`) {
fi.emit('data', character);
queueMicrotask(() => {
assert.strictEqual(output, expectations.shift());
output = '';
});
}
rli.close();
});
});
});

{
let output = '';
class FakeInput extends EventEmitter {
columns = 80

write = common.mustCall((data) => {
output += data;
}, 1)

resume() {}
pause() {}
end() {}
}

const fi = new FakeInput();
const rli = new readline.Interface({
input: fi,
output: fi,
terminal: true,
completer:
common.mustCallAtLeast(() => Promise.reject(new Error('message'))),
});

rli.on('line', common.mustNotCall());
fi.emit('data', '\t');
queueMicrotask(() => {
assert.match(output, /^Tab completion error: Error: message/);
output = '';
});
rli.close();
}
48 changes: 40 additions & 8 deletions test/parallel/test-readline-tab-complete.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,23 @@ common.skipIfDumbTerminal();
const width = getStringWidth(char) - 1;

class FakeInput extends EventEmitter {
columns = ((width + 1) * 10 + (lineBreak ? 0 : 10)) * 3
columns = ((width + 1) * 10 + (lineBreak ? 0 : 10)) * 3

write = common.mustCall((data) => {
output += data;
}, 6)
write = common.mustCall((data) => {
output += data;
}, 6)

resume() {}
pause() {}
end() {}
resume() {}
pause() {}
end() {}
}

const fi = new FakeInput();
const rli = new readline.Interface({
input: fi,
output: fi,
terminal: true,
completer: completer
completer: common.mustCallAtLeast(completer),
});

const last = '\r\nFirst group\r\n\r\n' +
Expand All @@ -68,3 +68,35 @@ common.skipIfDumbTerminal();
rli.close();
});
});

{
let output = '';
class FakeInput extends EventEmitter {
columns = 80

write = common.mustCall((data) => {
output += data;
}, 1)

resume() {}
pause() {}
end() {}
}

const fi = new FakeInput();
const rli = new readline.Interface({
input: fi,
output: fi,
terminal: true,
completer:
common.mustCallAtLeast((_, cb) => cb(new Error('message'))),
});

rli.on('line', common.mustNotCall());
fi.emit('data', '\t');
queueMicrotask(() => {
assert.match(output, /^Tab completion error: Error: message/);
output = '';
});
rli.close();
}

0 comments on commit 0861993

Please sign in to comment.