zachallaun
Raw terminal mode coming to OTP 28
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
Most Liked
garazdawi
It will not be possible to switch into raw mode once you have started an shell managed by :group, this is because the shell does various things to the terminal that are difficult (not impossible) to restore.
iex uses the built-in line editor that comes with Erlang, so if you want to do changes to what happens before you press “Enter” in iex, you need to do those in :edlin. It is already today possible to configure the keymappings of :edlin to whatever you like, so maybe getting vi mode into iex is just a matter of creating a correct keymap to edlin. I’m only a casual vi user, so I don’t know what you would expect from a vi mode.
iex could now with raw mode go its own way and implement its own edlin if the team wants to, though IMO it would be better if all efforts are put into a single line editor so that everyone (Erlang, Elixir, Gleam, Luerl etc) profit.
zachallaun
lawik
Last Post!
AndyL
As of today, the getch.exs demo by @zachallaun works with off-the-shelf tooling!
> asdf list elixir
1.18.3-otp-27
*1.18.4-otp-27
> asdf list erlang
27.1.1
*28.0
> iex --version
Erlang/OTP 28 [erts-16.0] [source] [64-bit] [smp:18:18] [ds:18:18:10] [async-threads:1] [jit:ns]
IEx 1.18.4 (compiled with Erlang/OTP 27)
Yahoo!!
Popular in Discussions
Other popular topics
Chat & Discussions>Discussions
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #hex
- #security










