IExHistory2 (IExHistory2 v5.4.0)
Improved shell history with variable binding persistance.
- Saves shell history between VM/shell restarts.
- Saves the shell variable bindings between VM/shell restarts.
- Ability to paste (most) terms into the shell (pids, references etc are handled)
- 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 application 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() - show summary / state
NOTE: To use he/1
the environment variable EDITOR
must be set to point to your editor:
export EDITOR="vim"
Navigation Keys
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 (21) - move up through history (see below).
ctrl^k (11) - move down through history (see below).
ctrl^h (08) - allows the currently displayed item to be modified.
ctrl^l (12) - opens the currently displayed item in an editor.
ctrl^[ (27) - reset navigation, returns to the prompt.
Text Editor
To use ctrl^l
the environment variable EDITOR
must be set to your editor of choice:
export EDITOR=vim
Standard Arrow Keys
If you want to use the regular up / down arrow (and backspace) keys:
Create the following file in your
HOME
directory:~/.erlang_keymap.config
[{stdlib, [{shell_keymap, #{ normal => #{ "\e[A" => none, "\e[B" => none } } }] }].
Set the following environment variable:
ERL_FLAGS='-config $HOME/.erlang_keymap.config'
Add the following to
IExHistory2
configuration:standard_arrow_keys: true
or
IExHistory2.initialize(standard_arrow_keys: true, ....)
Restart your VM
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 that
are bound in the shell to be accessible in an external module that has been loaded into 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}
The complimentary functions add_binding/3
and get_binding/2
that take a shell pid or registered name allowing
the user to debug applications.
defmodule VarTest do
def get_me(val) do
if IExHistory2.get_binding(:path_to_use, :myshell) == :path1 do
result = val + 100
IExHistory2.add_binding(:result_var, %{path: :path1, result: result}, :myshell)
result
else
result = val + 200
IExHistory2.add_binding(:result_var, %{path: :path2, result: result}, :myshell)
result
end
end
end
iex> spawn(fn -> VarTest.get_me(100) end)
#PID<0.1557.0>
%{path: :path2, result: 300}
iex> result_var
%{path: :path2, result: 300}
See also IExHistory2.register/1
.
Configuration
The following options can be set either as a keyword list in .iex.exs
(a sample file is
included in the github
repository):
[
colors: [
index: :red,
date: :green,
command: :yellow,
label: :red,
variable: :green,
binding: :cyan
],
command_display_width: 150,
hide_history_commands: true,
history_limit: :infinity,
import: true,
key_buffer_history: true,
navigation_keys: [
up: 21,
down: 11,
editor: 12,
modify: 8,
abandon: 27,
enter: 13
],
standard_arrow_keys: false,
paste_eval_regex: ["#Reference", "#PID", "#Function", "#Ecto.Schema.Metadata", "#Port"],
prepend_identifiers: true,
save_bindings: true,
save_invalid_results: false,
scope: :local,
show_date: true
]
Or in config/runtime.exs
:
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 issue with the current shell is the inconsistent ability to paste large terms into
the shell. Types such as process ids and references (#PID<0.1234.0>
) cause the evaluator to fail.
IExHistory2
will attempt to recognize and parse such terms during evaluation.
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 where the up-arrow key has to be pressed multiple times to
traverse a large term, IExHistory2
only requires a single up/down key, and the entire term can then
be edited.
The default navigation keys are defined above, but can be changed to any reasonable value. Please be aware
that certain key are reserved by the runtime and can not be used. The values should be set to decimal, the
example below sets opening the editor from ctrl^l
to ctrl^e
navigation_keys: [editor: 5]
To use standard up/down arrow keys (see Navigation Keys
above) set:
standard_arrow_keys: true
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, :global
or 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_name
i.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.
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.3"}
Or:
{:iex_history2, github: "nhpip/iex_history2", tag: "5.3.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()
Summary
Functions
Same as add_binding/2
, but name
is the registered name of a shell.
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.
The functions IExHistory2.add_binding/2
and IExHistory2.get_binding/1
allows variables that
are bound in the shell to be accessible in a module and vice-versa.
Same as get_binding/2
, but name
is the registered name of your shell. Useful
for debugging applications.
Returns the current shell bindings as a keyword list.
Show the variable bindings.
Copies the command at index 'i' and pastes it to the shell.
Displays the current state
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.
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.
Registers the shell under the name provided.
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
add_binding(var, value)
See IExHistory2.get_binding/1
.
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.
add_binding(var, value, name)
Same as add_binding/2
, but name
is the registered name of a shell.
See also register/1
and get_binding/2
clear()
Clears the history and bindings. If scope
is :global
the IEx session needs restarting for the changes to take effect.
clear_bindings()
Clears the bindings.
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.
configuration()
Displays the current configuration.
configure(kry, val)
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,
:colors
Examples:
IExHistory2.configure(:colors, [index: :blue])
IExHistory2.configure(:prepend_identifiers, true)
default_config()
Displays the default configuration.
get_binding(var)
The functions IExHistory2.add_binding/2
and IExHistory2.get_binding/1
allows variables that
are bound in the shell to be accessible in a module and vice-versa.
In this example the module was pasted into the shell.
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.
get_binding(var, name)
Same as get_binding/2
, but name
is the registered name of your shell. Useful
for debugging applications.
defmodule VarTest do
def get_me(val) do
if IExHistory2.get_binding(:path_to_use, :myshell) == :path1 do
result = val + 100
IExHistory2.add_binding(:result_var, %{path: :path1, result: result}, :myshell)
result
else
result = val + 200
IExHistory2.add_binding(:result_var, %{path: :path2, result: result}, :myshell)
result
end
end
end
iex> IExHistory2.register(:myshell)
true
iex> spawn(fn -> VarTest.get_me(100) end)
#PID<0.1557.0>
%{path: :path2, result: 300}
iex> result_var
%{path: :path2, result: 300}
See also IExHistory2.register/1
.
get_bindings()
Returns the current shell bindings as a keyword list.
Show the variable bindings.
hc(i)
Copies the command at index 'i' and pastes it to the shell.
he(i)
Displays the current state:
IExHistory2 version 5.3 is enabled:
Current history is 199 commands in size.
Current bindings are 153 variables in size.
Displays the entire history.
hl(val)
@spec hl(integer()) :: nil
Displays the entire history from the most recent entry back (negative number), or from the oldest entry forward (positive number)
hl(start, stop)
Specify a range.
iex> hl(10, 15)
hs(match)
@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.
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.
For large histories this command may take several seconds.
The original expression does not need to be a string.
iex> hsa("get_stte")
446: 92% 2024-02-04 23:27:16: :sys.get_state(Process.whereis(IExHistory2.Events.Server))
465: 92% 2024-02-05 00:57:04: :sys.get_state(Process.whereis(IExHistory2.Events.Server))
467: 92% 2024-02-05 00:57:38: :sys.get_state(Process.whereis(IExHistory2.Events.Server))
468: 92% 2024-02-05 00:58:25: :sys.get_state(Process.whereis(IExHistory2.Events.Server))
470: 92% 2024-02-05 00:59:17: :sys.get_state(Process.whereis(Server))
30: 83% 2024-02-03 20:22:41: :code.get_object_code(Types.UUID)
hsi(match)
@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.
hx(i)
Invokes the command at index 'i'.
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: [],
navigation_keys: [up: 21, down: 11, ...],
standard_arrow_keys: false,
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
is_enabled?()
Returns true
or false
depending on if history is enabled.
register(name)
@spec register(atom()) :: :ok
Registers the shell under the name provided.
start(config \\ default_config())
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.
unbind(vars)
Unbinds a variable or list of variables (specify variables as atoms, e.g. foo becomes :foo).