For anyone who’s worked on a terminal interface in Elixir, you may be excited to know that raw mode is coming to OTP 28: Implement lazy-read and noshell raw mode by garazdawi · Pull Request #8962 · erlang/otp · GitHub
If you’re unfamiliar with raw mode, the gist of it is that it allows terminals to read input without waiting for a newline, allowing for much more responsive terminal UIs. (There are drawbacks as well: You are fully responsible for what is printed, you lose all cursor movement (unless you re-implement it), et al.)
As a quick demo, here’s a script you can run if you have Erlang/OTP master installed:
# elixir getch.exs
defmodule Getch do
def run do
# new incantation to switch the terminal to raw mode in OTP 28
:shell.start_interactive({:noshell, :raw})
loop(nil)
end
def loop("q") do
IO.write("\rDone!")
System.halt(0)
end
def loop(last) do
if last, do: print(last)
loop(IO.getn("Next: ", 1))
end
def print(last) do
IO.write(IO.ANSI.format(["\r", "Got: ", :green, inspect(last), "\r\n"]))
end
end
Getch.run()
If you run this, you’ll notice that the script immediately responds to each character press. If you were to remove the :shell.start_interactive({:noshell, :raw})
and run the script using OTP 27, you’ll find the behavior to be quite different.
I’m really excited about this and hope to eventually incorporate some of this into Mneme. It seems like you can switch between raw and cooked modes interactively with ease, so it should be possible to “progressively enhance” terminal interfaces by switching to raw mode where you would normally accept input and then back to cooked mode afterwards. A quick example of such switching (use “s” to switch between modes):
defmodule Getch do
def run do
:shell.start_interactive({:noshell, :raw})
loop(nil, :raw)
end
def loop("q", _) do
IO.write("\rDone!")
System.halt(0)
end
def loop("s", :raw) do
:shell.start_interactive({:noshell, :cooked})
loop(nil, :cooked)
end
def loop("s", :cooked) do
:shell.start_interactive({:noshell, :raw})
loop(nil, :raw)
end
def loop(last, state) do
if last, do: print(last)
IO.write([IO.ANSI.clear_line(), "\r"])
loop(IO.getn("#{state}: ", 1), state)
end
def print(last) do
IO.write(IO.ANSI.format(["\r", "got ", :green, inspect(last), "\r\n"]))
end
end
Getch.run()
I’m excited to play around with this more and am eager to see/hear about what others are doing! Would love to hear from folks who have also done TUI work in Elixir. Some that come to mind: @fuelen @ausimian @AndyL