# iex_history2
Improved shell history withe variable binding persistance.
* Saves shell history between sessions.
* Saves the shell variable bindings between shell sessions.
* 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 accessed outside of scope of the shell to assist in code debugging.
* Can be enabled and state shared globally, or on individual shell sessions.
`IExHistory2` can be enabled in `~/.iex.exs` for example:
Code.append_path("~/github/iex_history2/_build/dev/lib/iex_history2/ebin")
IExHistory2.initialize(history_limit: 200, scope: :local, show_date: true, colors: [index: :red])
Alternatively the project can be added as a `Mix` dependancy.
## Navigation Keys
```
ctrl^u - Move up through history.
ctrl^k - Move down through history.
ctrl^e - Allows the currently displayed item to be modified.
ctrl^w - Opens the currently displayed item in an editor.
ctrl^[ - Reset navigation, returns to the prompt.
```
NOTE: To use `ctrl^w` the environment variable VISUAL must be set to point to the editor:
```
export VISUAL="vim"
```
## Shortcut Search and Edit Functions
Key history navigation functions are automatically imported into the shell.
```
iex> hl() - Will list the entire history.
iex> hl(val) - Will list `val` entries from the start if val is positive, or from the end if negative.
iex> hl(start, stop) - Will list entries between `start` and `stop`.
iex> hs(string) - Will list entries that match all or part the query string.
iex> hsi(string) - Case insensitive list entries that match all or part of the query string.
iex> hsa(string) - Closest match list of entries, e.g "acr.to_str" == "Macro.to_string"
iex> hx(pos) - Will execute the expression at position `pos`.
iex> hc(pos) - Will copy the expression at position pos to the shell.
iex> he(pos) - Edit the expression in a text editor.
iex> hb() - Displays the current bindings.
iex> hi() - Summary
```
NOTE: To use `he/1` the environment variable VISUAL must be set to point to the editor:
```
export VISUAL="vim"
```
### iex> hl()
Displays the entire history.
```
iex> hl()
1: 2023-09-01 17:29:27: time = Time.utc_now().second
2: 2023-09-01 17:29:31: time = Time.utc_now().second
3: 2023-09-01 17:29:36: time
4: 2023-09-01 17:29:41: new_time
5: 2023-09-01 17:50:10: Process.info self
6: 2023-09-01 17:50:33: r = o
7: 2023-09-01 17:52:36: Process.get(:iex_history)
```
### iex> hl(val)
If the argument is a positive integer it displays the command at that index.
If the argument is a negative number it displays the history that many items from the end.
```
iex> hl(2)
2: 2023-09-01 17:29:31: time = Time.utc_now().second
iex> IExHistory2.h(-3)
5: 2023-09-01 17:50:10: Process.info self
6: 2023-09-01 17:50:33: r = o
7: 2023-09-01 17:52:36: Process.get(:iex_history)
```
### iex> hl(start, stop)
Specify a range, the atoms :start and :stop can also be used.
### iex> hs(match)
Will search history for anything that matches the passed string.
```
iex> hs("Applic")
34: 2023-09-01 18:10:39: Application.put_env(:kernel, :shell_history, :disabled)
41: 2023-09-01 18:11:30: Application.get_env(:kernel, :shell_history)
48: 2023-09-01 18:14:02: Application.put_env(:kernel, :shell_history, 0)
101: 2023-09-01 19:01:15: :rpc.call(:erlang.node(Process.group_leader()), Application, :put_env, [:kernel, :shell_history, :disabled])
103: 2023-09-01 19:01:30: :rpc.call(:erlang.node(Process.group_leader()), Application, :put_env, [:kernel, :shell_history, :enabled])
```
### iex> hsi(match)
Case insensitive version of `hs/1`.
### iex> hsa(match, closeness \\ 80)
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.
```
iex> hsa("map_rdce")
786: 83% 2024-01-27 15:01:05: h(Enum.map_reduce)
806: 83% 2024-01-27 15:21:49: h(Enum.map_reduce)
826: 83% 2024-01-27 16:19:24: h(Enum.map_reduce)
```
### iex> hx(idx)
Invokes the command at index 'i'.
```
iex> hl(114)
114: 2023-09-01 19:30:14: Enum.count([1, 2, 3])
iex> hx(114)
3
```
### iex> hc(idx)
Copies the command at index 'i' and pastes it to the shell.
```
iex> hl(114)
114: 2023-09-01 19:30:14: Enum.count([1, 2, 3])
iex> hc(114)
:ok
iex> Enum.count([1, 2, 3])
```
### iex> he(idx)
Usefull for large terms or pasted modules. Will open the historical item in a text editor, ensuring
the result is re-evaluated and returned to the shell.
```
iex> he(114)
.....
.....
{:ok, :changes_made}
```
NOTE: To use `he/1` the environment variable VISUAL must be set to point to the editor:
```
export VISUAL="vim"
```
### iex> hb()
Shows the variable bindings.
### iex> hi()
Status summary.
## Binding Functions
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.
### IExHistory2.add_binding/2
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.
### IExHistory2.add_binding/2
The inverse of `add_binding/2`
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)
```
Experimental varients of `add_binding/2` and get_binding/1` exist that takes an atom that
is the registered name of a shell process identifier.
## Misc Functions
### IExHistory2.initialize(opts)
Initializes the IExHistory2 app. Takes the following parameters:
```
[
scope: :local,
history_limit: :infinity,
prepend_identifiers: true,
show_date: true,
save_invalid_results: false,
key_buffer_history: true,
save_bindings: true,
colors: [
index: :red,
date: :green,
command: :yellow,
label: :red,
variable: :green
]
]
```
### IExHistory2.clear()
Clears the history and bindings. If scope is :global the IEx session needs restarting for the changes to take effect.
### IExHistory2.clear_history(range)
Clears the history only, if no argument all history is cleared, else history from 1 to value is cleared
### IExHistory2.clear_bindings()
Clears bindings only
### IExHistory2.unbind(vars)
Unbinds a variable or list of variables, varibales should be expressed as atoms
### IExHistory2.stop_clear()
Clears the history and bindings then stops the service. If scope is :global the IEx session needs restarting for the changes to take effect.
### IExHistory2.configuration()
Displays the current conifuration
### IExHistory2.save_config(filename)
Saves the configuration to filename
### IExHistory2.load_config(filename)
Loads the configuration from filename.
NOTE: All changes may not be applied, to do this specify the filename in `IExHistory2.initialize/1` instead of a config keyword list
### IExHistory2.configure/2
Allows the following options to be changed, but not saved:
```
:show_date
:history_limit
:hide_history_commands,
:prepend_identifiers,
:command_display_width,
:save_invalid_results,
:key_buffer_history,
:save_bindings,
:colors
```
Examples:
```
IExHistory2.configure(:colors, [index: :blue])
IExHistory2.configure(:prepend_identifiers, true)
```
### IExHistory2.is_enabled?()
Returns true or false is IExHistory2 is enabled
## Configuration
The following options can be set:
[
scope: :local,
history_limit: :infinity,
hide_history_commands: true,
prepend_identifiers: true,
command_display_width: int,
save_invalid_results: false,
key_buffer_history: true,
show_date: true,
save_bindings: true,
colors: [
index: :red,
date: :green,
command: :yellow,
label: :red,
variable: :green
]
]
`:hide_history_commands` This will prevent all calls to IExHistory2.* from been saved.
NOTE: `IExHistory2.x/1` is always hidden. Scope of `:global` will only hide them from output, otherwise they will not be saved.
`:save_invalid_results` If set to false, the default, commands that were evaluated incorrectly will not be saved.
`:key_buffer_history` If set to true will allow the user to scroll up `(ctrl+u)` or down `(ctrl+k)` through history. Unlike the standard up/down arrow history this is command based not line based. So pasting of a large structure will only require 1 up or down. This mechanism also saves commands that were not properly evaluated; however there is a buffer limit of 75 lines, although this can be changed by updating `@history_buffer_size` in `events_server.ex`. This will also not duplicate back to back identical commands.
`:prepend_identifiers` If this is enabled it will prepend identifiers when a call to `x = IExHistory2(val)` is issued.
For example:
```
enabled:
iex> time = Time.utc_now().second
14
iex> new_time = IExHistory2.x(1)
22
iex> new_time
22 # New time is assigned to variable time
iex> time
13 # However, the original date variable is unchanged
iex> IExHistory2.h()
1: 2023-09-01 17:13:13: time = Time.utc_now().second
2: 2023-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 = IExHistory2.x(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> IExHistory2.h
1: 2023-09-01 17:17:43: time = Time.utc_now().second
2: 2023-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, :global` or a `node()` name
If `scope` is `: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
If `scope` is `node()` (e.g. `:mgr@localhost`) history will only be active on that shell
If `scope` is `:global` history will be shared between all shells. However the saving of variable bindings will be disabled along with the date/time in history
Furthermore, 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"
```