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 Apr 24, 2021
1 parent ff61371 commit 760d048
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 41 deletions.
13 changes: 6 additions & 7 deletions lib/internal/readline/callbacks.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ const {
} = primordials;

const {
codes
codes: {
ERR_INVALID_ARG_VALUE,
ERR_INVALID_CURSOR_POS,
},
} = require('internal/errors');

const {
ERR_INVALID_ARG_VALUE,
ERR_INVALID_CURSOR_POS,
} = codes;
const {
validateCallback,
} = require('internal/validators');
Expand All @@ -20,10 +19,10 @@ const {
} = require('internal/readline/utils');

const {
kClearLine,
kClearScreenDown,
kClearToLineBeginning,
kClearToLineEnd,
kClearLine,
kClearScreenDown
} = CSI;


Expand Down
2 changes: 1 addition & 1 deletion lib/internal/readline/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ function InterfaceConstructor(input, output, completer, terminal) {
);
}
}

if (signal) {
validateAbortSignal(signal, 'options.signal');
}
Expand Down
16 changes: 6 additions & 10 deletions lib/internal/readline/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ const {


/**
* moves the cursor to the x and y coordinate on the given stream
* Moves the cursor to the x and y coordinate on the given stream.
*/

function cursorTo(stream, x, y = undefined) {
if (NumberIsNaN(x)) return PromiseReject(new ERR_INVALID_ARG_VALUE('x', x));
if (NumberIsNaN(y)) return PromiseReject(new ERR_INVALID_ARG_VALUE('y', y));
Expand All @@ -46,9 +45,8 @@ function cursorTo(stream, x, y = undefined) {
}

/**
* moves the cursor relative to its current location
* Moves the cursor relative to its current location.
*/

function moveCursor(stream, dx, dy) {
if (stream == null || !(dx || dy)) {
return PromiseResolve();
Expand All @@ -72,14 +70,13 @@ function moveCursor(stream, dx, dy) {
}

/**
* clears the current line the cursor is on:
* Clears the current line the cursor is on:
* -1 for left of the cursor
* +1 for right of the cursor
* 0 for the entire line
*/

function clearLine(stream, dir) {
if (stream === null || stream === undefined) {
if (stream == null) {
return PromiseResolve();
}

Expand All @@ -89,11 +86,10 @@ function clearLine(stream, dir) {
}

/**
* clears the screen from the current position of the cursor down
* Clears the screen from the current position of the cursor down.
*/

function clearScreenDown(stream) {
if (stream === null || stream === undefined) {
if (stream == null) {
return PromiseResolve();
}

Expand Down
23 changes: 9 additions & 14 deletions lib/readline.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const {
ObjectDefineProperties,
ObjectSetPrototypeOf,
Promise,
PromiseReject,
StringPrototypeSlice,
} = primordials;

Expand Down Expand Up @@ -124,16 +125,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 @@ -381,7 +377,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 @@ -400,17 +397,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 @@ -387,10 +387,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 760d048

Please sign in to comment.