IExHistory2 (IExHistory2 v5.2.0)

Improved shell history with variable binding persistance.

  • Saves shell history between sessions.
  • Saves the shell variable bindings between VM restarts.
  • Ability to paste (most) terms into the shell.
  • Navigation keys allow history traversal where multi-line pastes require a single key up/down.
  • Shortcut functions permit search, pasting, re-evaluation and editing of items in history.
  • Editing can be done in-situ or in a text editor.
  • Shell variable bindings can be set/get outside of scope of the shell to assist in code debugging.
  • Can be enabled and state shared globally, or on individual shell sessions.

See section on Initialization and Configuration below.

Short-cut Functions

iex> hl()                     - list the entire history.

iex> hl(val)                  - list `val` entries from the start if val is positive, or from the end if negative.

iex> hl(start, stop)          - list entries between `start` and `stop`.

iex> hs(string)               - list entries that match all or part of the query string.

iex> hsi(string)              - case insensitive list entries that match all or part of the query string.

iex> hsa(string, dist \ 80)  - closest match list of entries, e.g "acr.to_str" == "Macro.to_string"

iex> hx(pos)                  - execute the expression at position `pos`.

iex> hc(pos)                  - copy the expression at position pos to the shell.

iex> he(pos)                  - edit the expression in a text editor.

iex> hb()                     - show the current bindings.

iex> hi()                     - summary

NOTE: To use he/1 the environment variable EDITOR must be set to point to your editor:

export EDITOR="vim"

Examples

Simple listing of last 9 items:

iex hl(-9)  
239: 2023-11-28 19:27:13: Ecto.Repo.Registry.lookup(CollectionServer.Server.Repo)
240: 2023-11-28 19:27:27: Ecto.Repo.Registry.lookup(CollectionServer.Server.Repo.ReadOnly1)
241: 2023-11-28 19:30:57: Ecto.Repo.Registry.all_running
242: 2023-11-28 20:55:22: DevHelper.port_stats(peer_port: 5432, statistics: :send_cnt)
243: 2023-11-28 20:55:34: Process.whereis(CSAdmin) |> :sys.get_status()
244: 2023-11-28 20:55:53: Process.get(self())
245: 2023-11-28 20:55:57: Process.get(CSAdmin)
246: 2023-11-29 14:49:21: CollectionServer.Server.Repo.all(q1)
247: 2023-11-29 17:48:21: %CollectionServer.FileSystem.Node{}

Partial match:

iex> hsa("Prcess.")
7: 90% 2023-11-12 16:29:03: c = fn -> {dict, _} = Process.info(pid, :dictionary); dict[:request_user] end
8: 90% 2023-11-12 16:29:37: c = fn -> {dict, _} = Process.info(pid, :dictionary) end
20: 90% 2023-11-26 16:32:11: Process.get(:yyy)
21: 90% 2023-11-26 22:11:50: Process.info(pid(0,619,0))
208: 90% 2023-11-28 14:19:23: Process.whereis(CollectionServer.Server.ReadOnly1)
209: 90% 2023-11-28 14:19:34: Process.whereis(CollectionServer.Server.ReadOnly1) |> Process.info
210: 90% 2023-11-28 14:20:02: Process.whereis(CollectionServer.Server.ReadOnly1) |> Process.info() |> Keyword.get(:links)
211: 90% 2023-11-28 14:20:14: Process.whereis(CollectionServer.Server.Repo) |> Process.info() |> Keyword.get(:links)

Special Functions

iex> IExHistory2.add_binding(var, val)

iex> IExHistory2.get_binding(var)

iex> IExHistory2.clear_history()

iex> IExHistory2.clear_bindings()

The functions IExHistory2.add_binding/2 and IExHistory2.get_binding/1 allows variables to be set in a module that is invoked in the shell to be accessible in the shell and vice versa.

defmodule VarTest do

  def get_me(val) do
    if IExHistory2.get_binding(:path_to_use) == :path1 do
      result = val + 100
      IExHistory2.add_binding(:result_var, %{path: :path1, result: result})
      result
    else
      result = val + 200
      IExHistory2.add_binding(:result_var, %{path: :path2, result: result})
      result
    end
  end

end

iex> path_to_use = :path1
:path1
iex> VarTest.get_me(50)
150
iex> result_var
%{path: :path1, result: 150}

iex> path_to_use = :path2
:path2
iex> VarTest.get_me(50)
250
iex> result_var
%{path: :path2, result: 250}

Navigation

The application uses a different set of keys for navigation, and attempts to present multi-line terms and other items as a single line:

ctrl^u    - move up through history.

ctrl^k    - move down through history.

ctrl^y    - allows the currently displayed item to be modified.

ctrl^e    - opens the currently displayed item in an editor.

ctrl^[    - reset navigation, returns to the prompt.

NOTE: To use ctrl^e the environment variable EDITOR must be set to your editor:

Configuration

The following options can be set either as a keyword list in .iex.exs.

[
  scope: :local,
  history_limit: :infinity,
  import: true,
  hide_history_commands: true,
  prepend_identifiers: true,
  command_display_width: int,
  save_invalid_results: false,
  key_buffer_history: true,
  paste_eval_regex: [],
  show_date: true,
  save_bindings: true,
  colors: [
    index: :red,
    date: :green,
    command: :yellow,
    label: :red,
    variable: :green
  ]
]

Or in config/runtime.exs if using Mix:

config :your_app, IExHistory2,
  scope: :local,
  history_limit: :infinity,
  paste_eval_regex: [],
  import: true,
  ...

Settings

To import short-cut functions set import: to true.

import: true

One current issue with the current shell is the inconsistent ability to paste large terms into the shell. Terms such as process ids and references (#PID<0.1234.0>) cause the evaluator to fail. The application will attempt to recognize such terms during evaluation, wrap them in quotes and attempt to re-evaluate.

Currently process ids, references, anonymous functions, ports and #Ecto.Schema.Metadata are supported by default. Additional terms can be added:

paste_eval_regex: ["#SpecialItem1", "#NewObject"]

This toggle true/false for calls to IExHistory2.* (and imports) from been saved.

hide_history_commands: true 

If set to false, the default, commands that were evaluated incorrectly will not be saved.

save_invalid_results: false 

If set to true will allow the user to scroll up (ctrl+u) or down (ctrl+k) through history.

key_buffer_history: true

Unlike the standard up/down arrow history this is command based not line based. So pasting of a large term or source code will only require 1 up or down key.

If this is enabled it will prepend identifiers when a call to x = hx(val) is issued.

prepend_identifiers: true

Example, enabled:

iex> time = Time.utc_now().second
14
iex> new_time = hx(1)
22

iex> new_time
22                  # New time is assigned to variable time
iex> time
13                  # However, the original date variable is unchanged

iex> hl()
1: 2021-09-01 17:13:13: time = Time.utc_now().second
2: 2021-09-01 17:13:22: new_time =  time = Time.utc_now().second    # We see the binding to new_time

Disabled:

iex> time = Time.utc_now().second
43
iex> new_time = hx(1)
50

iex> new_time       # New time is assigned to variable time
50
iex> time
50                  # However, this time the original time variable has also changed

iex> hl()
1: 2021-09-01 17:17:43: time = Time.utc_now().second
2: 2021-09-01 17:17:50: time = Time.utc_now().second      # We do not see the binding to new_time

scope: can be one of :local, :globalor a node name

  • :local (the default) history will be active on all shells, even those that are remotely connected, but the history for each shell will be unique

  • node_namei.e. (e.g. :mgr@localhost) history will only be active on that shell

  • :global history will be shared between all shells. However the saving of variable bindings will be disabled along with the date/time in history

If a scope of :global is selected following kernel option must be set, either directly as VM options or via an environment variable:

export ERL_AFLAGS="-kernel shell_history enabled"

--erl "-kernel shell_history enabled"

Initialization

Using .iex.exs

It is recommended to configure and start using .iex.exs, for example:

IExHistory2.initialize(history_limit: :infinity,
                       scope: :local, 
                       paste_eval_regex: ["#Extra"], 
                       show_date: true, 
                       colors: [index: :red])

As part of another application

Add to mix.exs as a dependency:

{:iex_history2, "~> 5.2"}

Or:

{:iex_history2, github: "nhpip/iex_history2", tag: "5.2.0"},

Add the configuration to your application config/runtime.exs. For example:

config :iex_history2,
  history_limit: 1234,
  import: true,
  scope: :local, 
  paste_eval_regex: ["#Extra"], 
  show_date: true, 
  colors: [index: :red]

When you connect your shell call IExHistory2.initialize/0 (in .iex.exs or as a standalone call):

IExHistory2.initialize()

NOTE: :scope of :global is not fully complete.

Summary

Functions

This helper function can be used when testing code (for example a module pasted into the shell). It allows a variable to be set that will become available in the shell. For example

Experimental. Same as add_binding/2, but name is the registered name of a shell pid.

Clears the history and bindings. If scope is :global the IEx session needs restarting for the changes to take effect.

Clears the bindings.

Clears the history only. If scope is :global the IEx session needs restarting for the changes to take effect. If a value is passed it will clear that many history entries from start, otherwise the entire history is cleared.

Displays the current configuration.

Allows the following options to be changed, but not saved

Displays the default configuration.

This helper function can be used when testing code (for example a module pasted into the shell). It allows a variable that is set in the shell to be available in a module under test. For example

Experimental. Same as get_binding/2, but name is the registered name of a shell pid.

Returns the current shell bindings.

Show the variable bindings.

Copies the command at index 'i' and pastes it to the shell.

Show history information summary.

Displays the entire history.

Displays the entire history from the most recent entry back (negative number), or from the oldest entry forward (positive number)

Specify a range, the atoms :start and :stop can also be used.

Returns the list of expressions where all or part of the string matches.

Like hsa/1 a case insensitive search, but also adds a closeness element to the search.

A case insensitive search the list of expressions where all or part of the string matches.

Invokes the command at index 'i'.

Initializes the IExHistory2 app. Takes the following parameters

Returns true or false depending on if history is enabled.

Loads the current configuration to file IExHistory2.save_config().

Registers the shell under the name provided.

Saves the current configuration to file.

Displays the current state

Clears the history and bindings then stops the service. If scope is :global the IEx session needs restarting for the changes to take effect.

Unbinds a variable or list of variables (specify variables as atoms, e.g. foo becomes :foo).

Functions

Link to this function

add_binding(var, value)

@spec add_binding(atom() | String.t(), any()) :: :ok

This helper function can be used when testing code (for example a module pasted into the shell). It allows a variable to be set that will become available in the shell. For example:

defmodule VarTest do
  def set_me(var) do
    var = var * 2
    IExHistory2.add_binding(:test_var, var)
    var + 100
  end
end

iex> VarTest.set_me(7)

iex> test_var
14

The variable can be represented as an atom or string.

Link to this function

add_binding(var, value, name)

@spec add_binding(atom() | String.t(), any(), atom()) :: :ok

Experimental. Same as add_binding/2, but name is the registered name of a shell pid.

See register/1

Clears the history and bindings. If scope is :global the IEx session needs restarting for the changes to take effect.

Link to this function

clear_bindings()

Clears the bindings.

Link to this function

clear_history(val \\ :all)

Clears the history only. If scope is :global the IEx session needs restarting for the changes to take effect. If a value is passed it will clear that many history entries from start, otherwise the entire history is cleared.

Link to this function

configuration()

Displays the current configuration.

Link to this function

configure(kry, val)

@spec configure(atom(), any()) :: atom()

Allows the following options to be changed, but not saved:

:show_date
:history_limit
:hide_history_commands,
:prepend_identifiers,
:save_bindings,
:command_display_width,
:save_invalid_results,
:key_buffer_history,
:colors

Examples:

IExHistory2.configure(:colors, [index: :blue])
IExHistory2.configure(:prepend_identifiers, true)
Link to this function

default_config()

Displays the default configuration.

Link to this function

get_binding(var)

@spec get_binding(atom() | String.t()) :: any()

This helper function can be used when testing code (for example a module pasted into the shell). It allows a variable that is set in the shell to be available in a module under test. For example:

defmodule VarTest do
  def get_me(val) do
    if IExHistory2.get_binding(:path_to_use) == :path1 do
      val + 100
    else
      val + 200
    end
  end     
end

iex> path_to_use = :path1
:path1
iex> VarTest.get_me(50)
150
iex> path_to_use = :path2
:path2
iex> VarTest.get_me(50)
250

The variable can be represented as an atom or string.

Link to this function

get_binding(var, name)

@spec get_binding(atom() | String.t(), atom()) :: any()

Experimental. Same as get_binding/2, but name is the registered name of a shell pid.

See register/1

Returns the current shell bindings.

Show the variable bindings.

@spec hc(integer()) :: any()

Copies the command at index 'i' and pastes it to the shell.

@spec he(integer()) :: any()

Show history information summary.

Displays the entire history.

@spec hl(integer()) :: nil

Displays the entire history from the most recent entry back (negative number), or from the oldest entry forward (positive number)

Link to this function

hl(start, stop)

@spec hl(integer(), integer()) :: nil

Specify a range, the atoms :start and :stop can also be used.

@spec hs(String.t()) :: nil

Returns the list of expressions where all or part of the string matches.

The original expression does not need to be a string.

Link to this function

hsa(match, closeness \\ 80)

@spec hsa(String.t(), integer()) :: nil

Like hsa/1 a case insensitive search, but also adds a closeness element to the search.

It uses a combination of Myers Difference and Jaro Distance to get close to a match. The estimated closeness is indicated in the result with a default range of > 80%. This can be set by the user.

For large histories this command may take several seconds.

The original expression does not need to be a string.

@spec hsi(String.t()) :: nil

A case insensitive search the list of expressions where all or part of the string matches.

The original expression does not need to be a string.

@spec hx(integer()) :: any()

Invokes the command at index 'i'.

Link to this function

initialize(config_or_filename \\ [])

Initializes the IExHistory2 app. Takes the following parameters:

[

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,
import: true,
paste_eval_regex: [],
save_bindings: true,
colors: [
  index: :red,
  date: :green,
  command: :yellow,
  label: :red,
  variable: :green
]

]

Alternatively a filename can be given that was saved with IExHistory2.save_config()

scope can be one of :local, :global or a node() name

Returns true or false depending on if history is enabled.

Link to this function

load_config(filename)

Loads the current configuration to file IExHistory2.save_config().

NOTE: Not all options can be set during run-time. Instead pass the filename as a single argument to IExHistory2.initialize()

@spec register(atom()) :: :ok

Registers the shell under the name provided.

Link to this function

save_config(filename)

Saves the current configuration to file.

Displays the current state:

IExHistory2 version 2.0 is enabled:
  Current history is 199 commands in size.
  Current bindings are 153 variables in size.

Clears the history and bindings then stops the service. If scope is :global the IEx session needs restarting for the changes to take effect.

Unbinds a variable or list of variables (specify variables as atoms, e.g. foo becomes :foo).