Skip to content

Commit 8bd6ab7

Browse files
DannyNemerMylesBorins
authored andcommitted
readline: add option to stop duplicates in history
Adds `options.deDupeHistory` for `readline.createInterface(options)`. If `options.deDupeHistory` is `true`, when a new input line being added to the history list duplicates an older one, removes the older line from the list. Defaults to `false`. Many users would appreciate this option, as it is a common setting in shells. This option certainly should not be default behavior, as it would be problematic in applications such as the `repl`, which inherits from the readline `Interface`. Extends documentation to reflect this API addition. Adds tests for when `options.deDupeHistory` is truthy, and when `options.deDupeHistory` is falsey. PR-URL: #2982 Reviewed-By: Jeremiah Senkpiel <[email protected]>
1 parent 62a8f47 commit 8bd6ab7

File tree

3 files changed

+73
-0
lines changed

3 files changed

+73
-0
lines changed

‎doc/api/readline.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,9 @@ added: v0.1.98
363363
`crlfDelay` milliseconds, both `\r` and `\n` will be treated as separate
364364
end-of-line input. Default to `100` milliseconds.
365365
`crlfDelay` will be coerced to `[100, 2000]` range.
366+
*`deDupeHistory`{boolean} If `true`, when a new input line added to the
367+
history list duplicates an older one, this removes the older line from the
368+
list. Defaults to `false`.
366369

367370
The `readline.createInterface()` method creates a new `readline.Interface`
368371
instance.

‎lib/readline.js‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ function Interface(input, output, completer, terminal){
3939

4040
EventEmitter.call(this);
4141
varhistorySize;
42+
vardeDupeHistory=false;
4243
letcrlfDelay;
4344
letprompt='> ';
4445

@@ -48,6 +49,7 @@ function Interface(input, output, completer, terminal){
4849
completer=input.completer;
4950
terminal=input.terminal;
5051
historySize=input.historySize;
52+
deDupeHistory=input.deDupeHistory;
5153
if(input.prompt!==undefined){
5254
prompt=input.prompt;
5355
}
@@ -80,6 +82,7 @@ function Interface(input, output, completer, terminal){
8082
this.output=output;
8183
this.input=input;
8284
this.historySize=historySize;
85+
this.deDupeHistory=!!deDupeHistory;
8386
this.crlfDelay=Math.max(kMincrlfDelay,
8487
Math.min(kMaxcrlfDelay,crlfDelay>>>0));
8588

@@ -249,6 +252,12 @@ Interface.prototype._addHistory = function(){
249252
if(this.line.trim().length===0)returnthis.line;
250253

251254
if(this.history.length===0||this.history[0]!==this.line){
255+
if(this.deDupeHistory){
256+
// Remove older history line if identical to new one
257+
constdupIndex=this.history.indexOf(this.line);
258+
if(dupIndex!==-1)this.history.splice(dupIndex,1);
259+
}
260+
252261
this.history.unshift(this.line);
253262

254263
// Only store so many

‎test/parallel/test-readline-interface.js‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,67 @@ function isWarned(emitter){
303303
returnfalse;
304304
});
305305

306+
// duplicate lines are removed from history when `options.deDupeHistory`
307+
// is `true`
308+
fi=newFakeInput();
309+
rli=newreadline.Interface({
310+
input: fi,
311+
output: fi,
312+
terminal: true,
313+
deDupeHistory: true
314+
});
315+
expectedLines=['foo','bar','baz','bar','bat','bat'];
316+
callCount=0;
317+
rli.on('line',function(line){
318+
assert.strictEqual(line,expectedLines[callCount]);
319+
callCount++;
320+
});
321+
fi.emit('data',expectedLines.join('\n')+'\n');
322+
assert.strictEqual(callCount,expectedLines.length);
323+
fi.emit('keypress','.',{name: 'up'});// 'bat'
324+
assert.strictEqual(rli.line,expectedLines[--callCount]);
325+
fi.emit('keypress','.',{name: 'up'});// 'bar'
326+
assert.notStrictEqual(rli.line,expectedLines[--callCount]);
327+
assert.strictEqual(rli.line,expectedLines[--callCount]);
328+
fi.emit('keypress','.',{name: 'up'});// 'baz'
329+
assert.strictEqual(rli.line,expectedLines[--callCount]);
330+
fi.emit('keypress','.',{name: 'up'});// 'foo'
331+
assert.notStrictEqual(rli.line,expectedLines[--callCount]);
332+
assert.strictEqual(rli.line,expectedLines[--callCount]);
333+
assert.strictEqual(callCount,0);
334+
rli.close();
335+
336+
// duplicate lines are not removed from history when `options.deDupeHistory`
337+
// is `false`
338+
fi=newFakeInput();
339+
rli=newreadline.Interface({
340+
input: fi,
341+
output: fi,
342+
terminal: true,
343+
deDupeHistory: false
344+
});
345+
expectedLines=['foo','bar','baz','bar','bat','bat'];
346+
callCount=0;
347+
rli.on('line',function(line){
348+
assert.strictEqual(line,expectedLines[callCount]);
349+
callCount++;
350+
});
351+
fi.emit('data',expectedLines.join('\n')+'\n');
352+
assert.strictEqual(callCount,expectedLines.length);
353+
fi.emit('keypress','.',{name: 'up'});// 'bat'
354+
assert.strictEqual(rli.line,expectedLines[--callCount]);
355+
fi.emit('keypress','.',{name: 'up'});// 'bar'
356+
assert.notStrictEqual(rli.line,expectedLines[--callCount]);
357+
assert.strictEqual(rli.line,expectedLines[--callCount]);
358+
fi.emit('keypress','.',{name: 'up'});// 'baz'
359+
assert.strictEqual(rli.line,expectedLines[--callCount]);
360+
fi.emit('keypress','.',{name: 'up'});// 'bar'
361+
assert.strictEqual(rli.line,expectedLines[--callCount]);
362+
fi.emit('keypress','.',{name: 'up'});// 'foo'
363+
assert.strictEqual(rli.line,expectedLines[--callCount]);
364+
assert.strictEqual(callCount,0);
365+
rli.close();
366+
306367
// sending a multi-byte utf8 char over multiple writes
307368
constbuf=Buffer.from('☮','utf8');
308369
fi=newFakeInput();

0 commit comments

Comments
(0)