Djunzu
Djunzu

Reputation: 498

How to remove duplicate commands from irb history?

I have searched several questions/answers/blogs without success. How to remove/delete duplicate commands from irb history?

Ideally I want to have the same behavior I configured for my bash. That is: after I execute a command every other entry in the history with the exactly same command is deleted.

But it would already be good to eliminate duplicates when I close irb.

My current .irbrc:

require 'irb/ext/save-history'
IRB.conf[:SAVE_HISTORY] = 1000
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb_history"
IRB.conf[:AUTO_INDENT] = true

Note: Ruby 2.4.1 (or newer!)

Upvotes: 2

Views: 533

Answers (2)

sshaw
sshaw

Reputation: 1014

An AT_EXIT hook is a perfectly acceptable way to do this. Though monkey patching is not needed. IRB provides facilities for doing this by creating your own input method.

IRB gets its input from an InputMethod. History is provided by the ReadlineInputMethod, which is a subclass.

InputMethods are attached to a Context. Entering conf in an irb session will give you access to the current context.

irb will read input according to the current context's io. For example:

irb [2.4.0] (screenstaring.com)$ conf.io
=> #<HistoryInputMethod:0x007fdc3403e180 @file_name="(line)", @line_no=3, @line=[nil, "4317.02 - 250 \n", "conf.id\n", "conf.io\n"], @eof=false, @stdin=#<IO:fd 0>, @stdout=#<IO:fd 1>, @prompt="irb [2.4.0] (screenstaring.com)$ ", @ignore_settings=[], @ignore_patterns=[]>

Uses my Bash-like history control class (for more info see below).

You can set conf.io to anything that conforms to the InputMethod interface:

conf.io = MyInputMethod.new

Whatever MyInputMethod#gets returns will be evaluated by IRB. Typically it reads from stdin.

To tell IRB to use your InputMethod at startup you can set the :SCRIPT config option:

# .irbrc
IRB.conf[:SCRIPT] = MyInputMethod.new

IRB will use :SCRIPT's value as input method when creating a Context. This can be set to a file to use its contents as the input method. By default it's nil, which results in stdin being used (via Readline, if it's available).

To create an input method that ignores duplicates override ReadlineInputMethod#gets:

class MyInputMethod < IRB::ReadlineInputMethod
  def gets
    line = super  # super adds line to HISTORY 
    HISTORY.pop if HISTORY[-1] == HISTORY[-2]
    line
  end
end

The InputMethod defined in my .irbrc allows one to set IRB_HISTCONTROL or IRB_HISTIGNORE like you would (more or less) for Bash:

IRB_HISTIGNORE=ignoreboth IRB_HISTCONTROL='\Aq!:foo' irb

This does the following:

  • Entries beginning with a space or duplicate (back-to-back) entries will not be added to the history
  • Entries beginning with q! (a custom method of mine) or containing foo will not be added to history

Upvotes: 2

Djunzu
Djunzu

Reputation: 498

This will eliminate duplicates after closing IRB console. But it works only for IRBs using Readline (mac users warned).

# ~/.irbrc
require 'irb/ext/save-history'
IRB.conf[:SAVE_HISTORY] = 1000
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb_history"

deduplicate_history = Proc.new do
    history = Readline::HISTORY.to_a
    Readline::HISTORY.clear
    history.reverse!.uniq!
    history.reverse!.each{|entry| Readline::HISTORY << entry}
end

IRB.conf[:AT_EXIT].unshift(deduplicate_history)

And this monkey patch will eliminate duplicates on the fly if your IRB is using Readline:

require 'irb/ext/save-history'
IRB.conf[:SAVE_HISTORY] = 1000
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb_history"

class IRB::ReadlineInputMethod
    alias :default_gets :gets
    def gets
        if result = default_gets
            line = result.chomp
            history = HISTORY.to_a
            HISTORY.clear
            history.each{|entry| HISTORY << entry unless entry == line}
            HISTORY << line
        end
        result
    end
end

Any suggestion on how to improve it?

Upvotes: 0

Related Questions