Skip to content

Commit 8ab26cf

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 17d16e8 commit 8ab26cf

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
@@ -370,6 +370,9 @@ changes:
370370
`crlfDelay` milliseconds, both `\r` and `\n` will be treated as separate
371371
end-of-line input. Default to `100` milliseconds.
372372
`crlfDelay` will be coerced to `[100, 2000]` range.
373+
*`deDupeHistory`{boolean} If `true`, when a new input line added to the
374+
history list duplicates an older one, this removes the older line from the
375+
list. Defaults to `false`.
373376

374377
The `readline.createInterface()` method creates a new `readline.Interface`
375378
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

@@ -257,6 +260,12 @@ Interface.prototype._addHistory = function(){
257260
if(this.line.trim().length===0)returnthis.line;
258261

259262
if(this.history.length===0||this.history[0]!==this.line){
263+
if(this.deDupeHistory){
264+
// Remove older history line if identical to new one
265+
constdupIndex=this.history.indexOf(this.line);
266+
if(dupIndex!==-1)this.history.splice(dupIndex,1);
267+
}
268+
260269
this.history.unshift(this.line);
261270

262271
// Only store so many

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

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

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

0 commit comments

Comments
(0)