readline,repl: improve history up/previous
Reaching the history end caused the last entry to be persistent. That way there's no actualy feedback to the user that the history end is reached. Instead, visualize the original input line and keep the history index at the history end in case the user wants to go back again. PR-URL: https://github.com/nodejs/node/pull/31112 Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
d449c505f3
commit
b52bf60518
@ -718,7 +718,7 @@ Interface.prototype._historyNext = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Interface.prototype._historyPrev = function() {
|
Interface.prototype._historyPrev = function() {
|
||||||
if (this.historyIndex < this.history.length) {
|
if (this.historyIndex < this.history.length && this.history.length) {
|
||||||
const search = this[kSubstringSearch] || '';
|
const search = this[kSubstringSearch] || '';
|
||||||
let index = this.historyIndex + 1;
|
let index = this.historyIndex + 1;
|
||||||
while (index < this.history.length &&
|
while (index < this.history.length &&
|
||||||
@ -727,9 +727,7 @@ Interface.prototype._historyPrev = function() {
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
if (index === this.history.length) {
|
if (index === this.history.length) {
|
||||||
// TODO(BridgeAR): Change this to:
|
this.line = search;
|
||||||
// this.line = search;
|
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
this.line = this.history[index];
|
this.line = this.history[index];
|
||||||
}
|
}
|
||||||
|
@ -482,12 +482,20 @@ function isWarned(emitter) {
|
|||||||
fi.emit('keypress', '.', { name: 'up' }); // 'baz'
|
fi.emit('keypress', '.', { name: 'up' }); // 'baz'
|
||||||
assert.strictEqual(rli.historyIndex, 2);
|
assert.strictEqual(rli.historyIndex, 2);
|
||||||
assert.strictEqual(rli.line, 'baz');
|
assert.strictEqual(rli.line, 'baz');
|
||||||
fi.emit('keypress', '.', { name: 'up' }); // 'baz'
|
fi.emit('keypress', '.', { name: 'up' }); // 'ba'
|
||||||
assert.strictEqual(rli.historyIndex, 2);
|
assert.strictEqual(rli.historyIndex, 4);
|
||||||
assert.strictEqual(rli.line, 'baz');
|
assert.strictEqual(rli.line, 'ba');
|
||||||
fi.emit('keypress', '.', { name: 'up' }); // 'baz'
|
fi.emit('keypress', '.', { name: 'up' }); // 'ba'
|
||||||
assert.strictEqual(rli.historyIndex, 2);
|
assert.strictEqual(rli.historyIndex, 4);
|
||||||
assert.strictEqual(rli.line, 'baz');
|
assert.strictEqual(rli.line, 'ba');
|
||||||
|
// Deactivate substring history search and reset history index.
|
||||||
|
fi.emit('keypress', '.', { name: 'right' }); // 'ba'
|
||||||
|
assert.strictEqual(rli.historyIndex, -1);
|
||||||
|
assert.strictEqual(rli.line, 'ba');
|
||||||
|
// Substring history search activated.
|
||||||
|
fi.emit('keypress', '.', { name: 'up' }); // 'ba'
|
||||||
|
assert.strictEqual(rli.historyIndex, 0);
|
||||||
|
assert.strictEqual(rli.line, 'bat');
|
||||||
rli.close();
|
rli.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,8 +78,7 @@ const tests = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
env: { NODE_REPL_HISTORY: defaultHistoryPath },
|
env: { NODE_REPL_HISTORY: defaultHistoryPath },
|
||||||
checkTotal: true,
|
test: [UP, UP, UP, UP, UP, DOWN, DOWN, DOWN, DOWN, DOWN],
|
||||||
test: [UP, UP, UP, UP, UP, DOWN, DOWN, DOWN, DOWN],
|
|
||||||
expected: [prompt,
|
expected: [prompt,
|
||||||
`${prompt}Array(100).fill(1).map((e, i) => i ** 2)`,
|
`${prompt}Array(100).fill(1).map((e, i) => i ** 2)`,
|
||||||
prev && '\n// [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, ' +
|
prev && '\n// [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, ' +
|
||||||
@ -92,6 +91,8 @@ const tests = [
|
|||||||
`${prompt}555 + 909`,
|
`${prompt}555 + 909`,
|
||||||
prev && '\n// 1464',
|
prev && '\n// 1464',
|
||||||
`${prompt}let ab = 45`,
|
`${prompt}let ab = 45`,
|
||||||
|
prompt,
|
||||||
|
`${prompt}let ab = 45`,
|
||||||
`${prompt}555 + 909`,
|
`${prompt}555 + 909`,
|
||||||
prev && '\n// 1464',
|
prev && '\n// 1464',
|
||||||
`${prompt}{key : {key2 :[] }}`,
|
`${prompt}{key : {key2 :[] }}`,
|
||||||
@ -138,9 +139,12 @@ const tests = [
|
|||||||
// UP - skipping const foo = true
|
// UP - skipping const foo = true
|
||||||
'\x1B[1G', '\x1B[0J',
|
'\x1B[1G', '\x1B[0J',
|
||||||
'> 555 + 909', '\x1B[12G',
|
'> 555 + 909', '\x1B[12G',
|
||||||
// UP, UP, ENTER. UPs at the end of the history have no effect.
|
// UP, UP
|
||||||
'\r\n',
|
// UPs at the end of the history reset the line to the original input.
|
||||||
'1464\n',
|
'\x1B[1G', '\x1B[0J',
|
||||||
|
'> 55', '\x1B[5G',
|
||||||
|
// ENTER
|
||||||
|
'\r\n', '55\n',
|
||||||
'\x1B[1G', '\x1B[0J',
|
'\x1B[1G', '\x1B[0J',
|
||||||
'> ', '\x1B[3G',
|
'> ', '\x1B[3G',
|
||||||
'\r\n'
|
'\r\n'
|
||||||
|
@ -10,6 +10,7 @@ const assert = require('assert');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
|
const util = require('util');
|
||||||
|
|
||||||
const tmpdir = require('../common/tmpdir');
|
const tmpdir = require('../common/tmpdir');
|
||||||
tmpdir.refresh();
|
tmpdir.refresh();
|
||||||
@ -51,6 +52,7 @@ ActionStream.prototype.readable = true;
|
|||||||
|
|
||||||
// Mock keys
|
// Mock keys
|
||||||
const UP = { name: 'up' };
|
const UP = { name: 'up' };
|
||||||
|
const DOWN = { name: 'down' };
|
||||||
const ENTER = { name: 'enter' };
|
const ENTER = { name: 'enter' };
|
||||||
const CLEAR = { ctrl: true, name: 'u' };
|
const CLEAR = { ctrl: true, name: 'u' };
|
||||||
|
|
||||||
@ -90,20 +92,42 @@ const tests = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
env: {},
|
env: {},
|
||||||
test: [UP, '\'42\'', ENTER],
|
test: [UP, '21', ENTER, "'42'", ENTER],
|
||||||
expected: [prompt, '\'', '4', '2', '\'', '\'42\'\n', prompt, prompt],
|
expected: [
|
||||||
|
prompt,
|
||||||
|
'2', '1', '21\n', prompt, prompt,
|
||||||
|
"'", '4', '2', "'", "'42'\n", prompt, prompt
|
||||||
|
],
|
||||||
clean: false
|
clean: false
|
||||||
},
|
},
|
||||||
{ // Requires the above test case
|
{ // Requires the above test case
|
||||||
env: {},
|
env: {},
|
||||||
test: [UP, UP, ENTER],
|
test: [UP, UP, CLEAR, ENTER, DOWN, CLEAR, ENTER, UP, ENTER],
|
||||||
expected: [prompt, `${prompt}'42'`, '\'42\'\n', prompt]
|
expected: [
|
||||||
|
prompt,
|
||||||
|
`${prompt}'42'`,
|
||||||
|
`${prompt}21`,
|
||||||
|
prompt,
|
||||||
|
prompt,
|
||||||
|
`${prompt}'42'`,
|
||||||
|
prompt,
|
||||||
|
prompt,
|
||||||
|
`${prompt}21`,
|
||||||
|
'21\n',
|
||||||
|
prompt,
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
env: { NODE_REPL_HISTORY: historyPath,
|
env: { NODE_REPL_HISTORY: historyPath,
|
||||||
NODE_REPL_HISTORY_SIZE: 1 },
|
NODE_REPL_HISTORY_SIZE: 1 },
|
||||||
test: [UP, UP, CLEAR],
|
test: [UP, UP, DOWN, CLEAR],
|
||||||
expected: [prompt, `${prompt}'you look fabulous today'`, prompt]
|
expected: [
|
||||||
|
prompt,
|
||||||
|
`${prompt}'you look fabulous today'`,
|
||||||
|
prompt,
|
||||||
|
`${prompt}'you look fabulous today'`,
|
||||||
|
prompt
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
env: { NODE_REPL_HISTORY: historyPathFail,
|
env: { NODE_REPL_HISTORY: historyPathFail,
|
||||||
@ -169,6 +193,8 @@ function runTest(assertCleaned) {
|
|||||||
const opts = tests.shift();
|
const opts = tests.shift();
|
||||||
if (!opts) return; // All done
|
if (!opts) return; // All done
|
||||||
|
|
||||||
|
console.log('NEW');
|
||||||
|
|
||||||
if (assertCleaned) {
|
if (assertCleaned) {
|
||||||
try {
|
try {
|
||||||
assert.strictEqual(fs.readFileSync(defaultHistoryPath, 'utf8'), '');
|
assert.strictEqual(fs.readFileSync(defaultHistoryPath, 'utf8'), '');
|
||||||
@ -193,6 +219,7 @@ function runTest(assertCleaned) {
|
|||||||
output: new stream.Writable({
|
output: new stream.Writable({
|
||||||
write(chunk, _, next) {
|
write(chunk, _, next) {
|
||||||
const output = chunk.toString();
|
const output = chunk.toString();
|
||||||
|
console.log('INPUT', util.inspect(output));
|
||||||
|
|
||||||
// Ignore escapes and blank lines
|
// Ignore escapes and blank lines
|
||||||
if (output.charCodeAt(0) === 27 || /^[\r\n]+$/.test(output))
|
if (output.charCodeAt(0) === 27 || /^[\r\n]+$/.test(output))
|
||||||
@ -207,7 +234,7 @@ function runTest(assertCleaned) {
|
|||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
prompt: prompt,
|
prompt,
|
||||||
useColors: false,
|
useColors: false,
|
||||||
terminal: true
|
terminal: true
|
||||||
}, function(err, repl) {
|
}, function(err, repl) {
|
||||||
|
@ -8,6 +8,7 @@ const assert = require('assert');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
|
const util = require('util');
|
||||||
|
|
||||||
const tmpdir = require('../common/tmpdir');
|
const tmpdir = require('../common/tmpdir');
|
||||||
tmpdir.refresh();
|
tmpdir.refresh();
|
||||||
@ -49,6 +50,7 @@ ActionStream.prototype.readable = true;
|
|||||||
|
|
||||||
// Mock keys
|
// Mock keys
|
||||||
const UP = { name: 'up' };
|
const UP = { name: 'up' };
|
||||||
|
const DOWN = { name: 'down' };
|
||||||
const ENTER = { name: 'enter' };
|
const ENTER = { name: 'enter' };
|
||||||
const CLEAR = { ctrl: true, name: 'u' };
|
const CLEAR = { ctrl: true, name: 'u' };
|
||||||
|
|
||||||
@ -88,20 +90,40 @@ const tests = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
env: {},
|
env: {},
|
||||||
test: [UP, '\'42\'', ENTER],
|
test: [UP, '21', ENTER, "'42'", ENTER],
|
||||||
expected: [prompt, '\'', '4', '2', '\'', '\'42\'\n', prompt, prompt],
|
expected: [
|
||||||
|
prompt,
|
||||||
|
// TODO(BridgeAR): The line is refreshed too many times. The double prompt
|
||||||
|
// is redundant and can be optimized away.
|
||||||
|
'2', '1', '21\n', prompt, prompt,
|
||||||
|
"'", '4', '2', "'", "'42'\n", prompt, prompt
|
||||||
|
],
|
||||||
clean: false
|
clean: false
|
||||||
},
|
},
|
||||||
{ // Requires the above test case
|
{ // Requires the above test case
|
||||||
env: {},
|
env: {},
|
||||||
test: [UP, UP, ENTER],
|
test: [UP, UP, UP, DOWN, ENTER],
|
||||||
expected: [prompt, `${prompt}'42'`, '\'42\'\n', prompt]
|
expected: [
|
||||||
|
prompt,
|
||||||
|
`${prompt}'42'`,
|
||||||
|
`${prompt}21`,
|
||||||
|
prompt,
|
||||||
|
`${prompt}21`,
|
||||||
|
'21\n',
|
||||||
|
prompt
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
env: { NODE_REPL_HISTORY: historyPath,
|
env: { NODE_REPL_HISTORY: historyPath,
|
||||||
NODE_REPL_HISTORY_SIZE: 1 },
|
NODE_REPL_HISTORY_SIZE: 1 },
|
||||||
test: [UP, UP, CLEAR],
|
test: [UP, UP, DOWN, CLEAR],
|
||||||
expected: [prompt, `${prompt}'you look fabulous today'`, prompt]
|
expected: [
|
||||||
|
prompt,
|
||||||
|
`${prompt}'you look fabulous today'`,
|
||||||
|
prompt,
|
||||||
|
`${prompt}'you look fabulous today'`,
|
||||||
|
prompt
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
env: { NODE_REPL_HISTORY: historyPathFail,
|
env: { NODE_REPL_HISTORY: historyPathFail,
|
||||||
@ -167,6 +189,8 @@ function runTest(assertCleaned) {
|
|||||||
const opts = tests.shift();
|
const opts = tests.shift();
|
||||||
if (!opts) return; // All done
|
if (!opts) return; // All done
|
||||||
|
|
||||||
|
console.log('NEW');
|
||||||
|
|
||||||
if (assertCleaned) {
|
if (assertCleaned) {
|
||||||
try {
|
try {
|
||||||
assert.strictEqual(fs.readFileSync(defaultHistoryPath, 'utf8'), '');
|
assert.strictEqual(fs.readFileSync(defaultHistoryPath, 'utf8'), '');
|
||||||
@ -192,6 +216,7 @@ function runTest(assertCleaned) {
|
|||||||
output: new stream.Writable({
|
output: new stream.Writable({
|
||||||
write(chunk, _, next) {
|
write(chunk, _, next) {
|
||||||
const output = chunk.toString();
|
const output = chunk.toString();
|
||||||
|
console.log('INPUT', util.inspect(output));
|
||||||
|
|
||||||
// Ignore escapes and blank lines
|
// Ignore escapes and blank lines
|
||||||
if (output.charCodeAt(0) === 27 || /^[\r\n]+$/.test(output))
|
if (output.charCodeAt(0) === 27 || /^[\r\n]+$/.test(output))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user