Command history on custom Elixir CLI not working with OTP 26, but was with OTP 25

Hi,

Elixir version. 1.16.3
OTP Version: 26.2.5

I have a custom CLI that is no longer working after upgrading to OTP 26. The up arrow that gets the previous commands does not work anymore, and I am looking for some help to fix it. I have also posted this on the elixir forum: Command history on custom CLI not working with OTP 26, but was with OTP 25 - Questions / Help - Elixir Programming Language Forum

I have created a slimmed down version of my custom CLI to show and reproduce the problem: alanj853/cli_test: Test repo for reproducing problems with OTP 26 (github.com)

Here are the steps (they are also in the readme of that repo):

  1. Clone this repo and run install the following elixir and erlang versions using asdf
asdf install erlang 25.3.2
asdf install erlang 26.2.5
asdf install elixir 1.16.3-otp-25
asdf install elixir 1.16.3-otp-26
  1. First try with OTP 25.
asdf local erlang 25.3.2
asdf local elixir 1.16.3-otp-25
  1. Run “iex -S mix” This should launch you into the custom CLI
  2. Type the command “hello” You should get response “world”
  3. Now press the up arrow on the keyboard to get the “hello” command again

Expected result: “hello” command should come up on the prompt

Actual result: On OTP 25, the up arrow does as expected:

Welcome to the CLI
myprompt> hello
world
myprompt> hello
world
myprompt>
  1. Now try with OTP 26
asdf local erlang 26.2.5
asdf local elixir 1.16.3-otp-26
  1. Run “iex -S mix” This should launch you into the custom CLI
  2. Type the command “hello” You should get response “world”
  3. Now press the up arrow on the keyboard to get the “hello” command again

Expected result: “hello” command should come up on the prompt

Actual result: On OTP 26, the up arrow seemingly does nothing

Welcome to the CLI
myprompt> hello
world
myprompt>

Hello!

This is most likely due to stdlib: enhances the shell completion by frazze-jobb · Pull Request #5924 · erlang/otp · GitHub which was introduced as part of our shell completions enhancements in Erlang/OTP 26.

The workaround for now is to use I/O get_until requests, which is what both Erlang and Elixir does. The relevant code in Elixir is here: elixir/lib/iex/lib/iex/server.ex at v1.17 · elixir-lang/elixir · GitHub

For 28 I’m planning to make a better API that makes it more explicit what is saved in the history and what is not, but that is 8 months away.

EDIT: Just found this issue: `io:get_line/1` input not added to shell history · Issue #6896 · erlang/otp · GitHub seems like Elixir had a similar problem as you.

1 Like

Thank you very much, this seems to have solved it. As you suggested, I simply replaced my IO.gets/1 call with a get_until call.

For clarity for anyone else reading this, here is the code change, replacing IO.gets/1

Before:

  defp get_input(message, %{input_type: :visible}) do
    ensure_string_result(message, &IO.gets/1)
  end

After:

  def gets(device \\ :stdio, prompt) do
    :io.request(map_dev(device), {:get_until, :unicode, to_chardata(prompt), __MODULE__, :__parse__, []})
  end

  defp to_chardata(list) when is_list(list), do: list
  defp to_chardata(other), do: to_string(other)

  @doc false
  def __parse__([], :eof), do: {:done, :eof, []}
  def __parse__([], chars), do: {:done, List.to_string(chars), []}

  # Map the Elixir names for standard IO and error to Erlang names
  defp map_dev(:stdio), do: :standard_io
  defp map_dev(:stderr), do: :standard_error
  defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other

  defp get_input(message, %{input_type: :visible}) do
    ensure_string_result(message, &gets/1)
  end
1 Like