zachallaun

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

garazdawi

Erlang Core Team

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

zachallaun

Here’s what that last demo looks like:

getch

lawik

lawik

Nerves Core Team

Owl can get a lot more live now :slight_smile:

Last Post!

AndyL

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!!

Where Next?

Popular in Discussions Top

New
AlexMcConnell
The reason that Rails is as popular as it is is because it’s very easy for relatively inexperienced developers to get a lot of work done....
588 20046 166
New
AstonJ
If a newbie asked you about Phoenix Contexts, how would you explain the basics to them? Feel free to be as concise or in-depth as you li...
New
chuck
Let me start by stating an assumption: Phoenix is a great approach to building REST APIs. There are many reasons for this, but I will ass...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
owaisqayum
I have a sample string sentence = "Hello, world ... 123 *** ^%&*())^% %%:>" From this string, I want to only keep the integers, ...
New
fireproofsocks
I’ve been working on an Elixir project that has required a lot of scripting. I usually reach for Elixir because I like it more (and in th...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36654 110
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42533 114
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

We're in Beta

About us Mission Statement