Improved? IEx shell history. My pet project/hack session

Hi,

I wrote an initial version of this a month or 2 ago. I recently improved on it after I crashed my IEx shell at work.

Features:

1 Persist history
2 Persist the shell variable bindings (that get reloaded when you restart)
2 List history
3 Search history
4 Re-execute a command in history
5. Scoping rules
6. Update configuration

I probably broke some rules on how it was implemented, but it works for me. Feel free to play with it…Next on my list is to make it play better with the current IEx history.

Pics attached
Screen Shot 2021-09-01 at 3.48.12 PM
Screen Shot 2021-09-01 at 3.51.45 PM
Screen Shot 2021-09-01 at 3.55.48 PM
Screen Shot 2021-09-01 at 3.52.28 PM
Screen Shot 2021-09-01 at 3.53.04 PM

Display history

15 Likes

Added a few improvements:

  1. Can set the max width when displaying output
  2. Copy/pasting now works
  3. If an operation in not correctly evaluated you have the option to not save it
2 Likes

Love a nice bit of code hacking.

Are you aware that you can do the following (in my case in .bashrc) and it will persist history?
export ERL_AFLAGS="-kernel shell_history enabled"

Between that and the normal readline shortcuts like ctrl+r I think most of what you’re looking for is built-in.

OTOH, if you’re building it for fun - nice work!

3 Likes

Thanks,

The problem withe ctrl+r is that it’s a line history, not a command history. So if, for example, you paste a large list, map or struct into the shell it renders it useless. I use the kernel flags too, but that’s a globally shared view which is fine in some cases, but not always (I also don’t think it includes date etc…plus I haven’t found a nice way to just show the history.

Still, as you said … it’s all for fun

4 Likes

OK, latest version:

In addition to saving history and bindings, this version adds:

  1. Supports for aliasing the History module

  2. Copy/paste a command from history to the shell (so you can edit it)

  3. Use ctrl+u and ctrl+k to move up and down history. Unlike the standard history search with the arrow keys this is command not line based. So, for example:

Paste:

iex(1)> config = [
...(1)>   scope: :local,
...(1)>   history_limit: :infinity,
...(1)>   hide_history_commands: true,
...(1)>   prepend_identifiers: true,
...(1)>   key_buffer_history: true,
...(1)>   command_display_width: :int,
...(1)>   save_invalid_results: false,
...(1)>   show_date: true,
...(1)>   save_bindings: true,
...(1)>   colors: [
...(1)>     index: :red,
...(1)>     date: :green,
...(1)>     command: :yellow,
...(1)>     label: :red,
...(1)>     variable: :green
...(1)>   ]
...(1)> ]
[
  scope: :local,
  history_limit: :infinity,
  hide_history_commands: true,
  prepend_identifiers: true,
  key_buffer_history: true,
  command_display_width: :int,
  save_invalid_results: false,
  show_date: true,
  save_bindings: true,
  colors: [
    index: :red,
    date: :green,
    command: :yellow,
    label: :red,
    variable: :green
  ]
]

ctrl+u:

iex(2)> config = [ scope: :local, history_limit: :infinity, hide_history_commands: true, prepend_identifiers: true, key_buffer_history: true, command_display_width: :int, save_invalid_results: false, show_date: true, save_bindings: true, colors: [ index: :red, date: :green, command: :yellow, label: :red, variable: :green ]]
3 Likes

OK, fixed a bad bug where it would not work on the latest version of Elixir.

2 Likes

Just now looked at this after I had it in my TODO things to check for 14 months. :smiley:

I am interested as to why are you recommending to use the dev build in your README?

Ideally we should just have a script that does something like mix do deps.get, release and then copies the final release to a well-known place and use it from there? Not sure. If you accept PRs I could look into it because I am very interested in nicer iex history, especially one that’s not line-bound but is working on complete statements that are valid Elixir code.

OMG please don’t let the code scare you, it was my first personal elixir project after over a decade of Erlang. As for the dev comment, a couple of guys from work tried it and I didn’t want (thinking of work) someone to add it to our product’s deps and pushing it to our AWS EC2 instances. I should probably edit the README. Feel free to do PRs if you want. I warn you that it does some weird back door stuff. One thing I like about it is ability to save shell variable bindings.

Well, 50/50, I am not sure yet I’d post PRs if you are not willing to further develop this and/or make it more current (or something).

But another question: any clue which exact Erlang module and function to use to browse history? Or is that only for Erlang and iex uses something differently? Looking at iex source code didn’t net any insights on a quick look.

I’m happy to continue to maintain it. Erlang uses the shell module and all history is saved via the disk_log module. However, the OTP team is re-writing a lot of the shell, so that may change. What I do is attach a trace to the iex shell’s iex_server process to capture and save events.

1 Like

Tried to look into this in more detail. Is this the whole meat and potatoes of actually fetching the currently globally known shell history?

:rpc.call(:erlang.node(:erlang.group_leader()), :group_history, :load, [])

Or is there more to it? I executed this in my iex REPL and it seems to give me all history items indeed. Unfortunately it doesn’t merge multi-line expressions into one… :confused:

I am aware that the :shell and IEx.History modules allow you to have separate managed histories but I am only interested in the global one. Currently trying to find where exactly on disk is the global Erlang shell history preserved, to no avail so far. I can’t even find any mention of the group_history atom anywhere on erlang.org

With the current Erlang history that’s it. Basically the :rpc.call gets the history for the VM the remote shell was started by (rather than the one it is remote shell connected to). The call :group_history.load/0 basically dumps out the contents of the Erlang disk logger.

In the case of this project I added the :global feature for folks that like the existing shell. Tempted to remove it.

1 Like

Ha, nice. Of course it’s called something like group_history so I can’t easily find it… :003:

I also got a reply on ErlangForums: Where is the global shell history stored? - Questions / Help - Erlang Programming Language Forum - Erlang Forums

Thank you!

EDIT: That being said, I can’t find the docs of group_history at all but this link seems to contain the source: erlang-history/src/5.2/group_history.erl at master · ferd/erlang-history · GitHub