Skip to content

Instantly share code, notes, and snippets.

@sushant12
Last active November 7, 2023 17:53
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sushant12/38c8e01ab7a3673ca45abe82d1ffe3c0 to your computer and use it in GitHub Desktop.
Save sushant12/38c8e01ab7a3673ca45abe82d1ffe3c0 to your computer and use it in GitHub Desktop.
list or search through your iex history
# The bash command `history | grep abc` is a neat tool. I abuse it alot.
# So, I wrote a module that would list or search through iex history.
# https://dev.to/sushant12/list-all-history-in-iex-408b
# I prefer to put this module in my .iex.exs file and then import the .iex.exs file into my project.
defmodule History do
def search do
load_history()
|> Enum.with_index(1)
|> Enum.each(fn {value, index} ->
IO.write("#{index} #{value}")
end)
end
def search(term) do
load_history()
|> Enum.filter(&String.match?(&1, ~r/#{term}/))
|> Enum.with_index(1)
|> Enum.each(fn {value, index} ->
IO.write("#{index} ")
IO.write(String.replace(value, term, "#{IO.ANSI.red()}#{term}#{IO.ANSI.default_color()}"))
end)
end
def search(term, opts) do
history = load_history()
history_count = Enum.count(history)
history
|> get_match_indices(term)
|> maybe_add_contexts(opts, history_count)
|> group_ranges()
|> Enum.each(fn range_list ->
parse_range(range_list)
|> Enum.each(fn match_index ->
if match_index < history_count do
txt = Enum.at(history, match_index)
IO.write("#{match_index} ")
IO.write(String.replace(txt, term, "#{IO.ANSI.red()}#{term}#{IO.ANSI.default_color()}"))
end
end)
IO.write("#{IO.ANSI.red()}--- #{IO.ANSI.default_color()} \n")
end)
end
defp group_ranges([head | rest]) do
Enum.reduce(rest, {head, [], []}, fn current, {prev, group, grouped} ->
if Range.disjoint?(current, prev) do
{current, [current], [Enum.reverse(group) | grouped]}
else
{current, [current | group], grouped}
end
end)
|> elem(2)
|> Enum.reverse()
end
defp get_match_indices(history, term) do
history
|> Enum.with_index()
|> Enum.flat_map(fn {element, index} ->
case String.match?(element, ~r/#{term}/) do
true -> [index]
false -> []
end
end)
end
defp maybe_add_contexts(match_indices, opts, history_count) do
context_a = Keyword.get(opts, :A, 0)
context_b = Keyword.get(opts, :B, 0)
match_indices
|> Enum.map(fn index ->
upper_bound(index, context_b)..lower_bound(index, context_a, history_count)
end)
end
defp upper_bound(index, 0), do: index
defp upper_bound(index, context_b) do
potential_index = index - context_b
if potential_index < 0 do
0
else
potential_index
end
end
defp lower_bound(index, 0, _), do: index
defp lower_bound(index, context_a, history_count) do
potential_index = index + context_a
if potential_index > history_count do
history_count
else
potential_index
end
end
defp parse_range(range_list) do
Enum.map(range_list, &Enum.to_list/1)
|> List.flatten()
|> Enum.uniq()
end
defp load_history do
:group_history.load()
|> Enum.map(&List.to_string/1)
|> Enum.reverse()
end
end
@eksperimental
Copy link

eksperimental commented Dec 21, 2021

I love this function @sushant12. I have been using it for a while.
Any chance of adding some options such as -A, -B, -C like grep, where you can show lines before, after or surrounding?

@sushant12
Copy link
Author

@eksperimental

Any chance of adding some options such as -A, -B, -C like grep, where you can show lines before, after or surrounding?

I wasnt aware of these options but I am looking into it now. Let me spend more time reading the docs and get back to you after the weekend.

PS: Thanks for your contribution to elixir 😄

@eksperimental
Copy link

You are welcome!

Good, let me know if you come up with something or if you need any help with it. Glad to give it a try or contribute to it.

@sushant12
Copy link
Author

@eksperimental I have went through the docs for those flags and I found those flags to be very useful.

Do you think this approach is okay?

History.search("Repo", "-A 5")

I will then pattern match on the 2nd param like. "-A " <> num, "-B" <> num

Let me know what you think of this.

@eksperimental
Copy link

I think an options argument being it a keyword list is the best aproach.

History.search("Repo", A: 5)

Also if you make an alias named history/2 that resolves to search/2,
we can add it as a helper in an .iexs files.

import History

iex> history "Enum.reduce", C: 5

@sushant12
Copy link
Author

acknowledged. I will implement this and I will ping back when I am done with one of the flag. 👨‍💻

@sushant12
Copy link
Author

finished implementing for -A flag. I will add the new changes here after I am done with some code changes. ✌️

@sushant12
Copy link
Author

sushant12 commented Feb 18, 2022

Hi @eksperimental

My apologies for updating you about the progress after months. Life just happened so I couldn't focus.

I have finally finished adding contexts to History.search/2 and please note that I didn't add the -C flag because I got too damn lazy 😅

You can now use it like this

History.search("Repo.get!", A: 2, B: 2)

@eksperimental
Copy link

Awesome.
I will use it and get back to you.
BTW, have you consider the possibility of having this as a repository? I will be easier to contribute to, and maybe release it as an Elixir package.

@sushant12
Copy link
Author

sushant12 commented Feb 19, 2022

@eksperimental

have you consider the possibility of having this as a repository?

Yes, I did think about it but I could not convince myself regarding its usefulness as a package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment