I have a console application in Elixir. I need to interpret user’s input on by keypress basis. For instance, I need to treat “q” as a command to end the session, without user to explicitly press ⏎ a.k.a. “carriage return.”
IO.getn/2
surprisingly waits for the ⏎ to be pressed, buffering an input (I am nearly sure, that this buffering is done by console itself, but man stty
does not provide any help/flag to turn buffering off.)
Mix.Utils
use the infinite loop to hide user input (basically sending backspace control sequence to console every 1ms,) IEx
code wraps calls to standard erlang’s io
, that provides the only ability to set a callback on Tab (for autocompletion.)
My guess would be I have to use Port
, attach it to :stdin
and spawn a process to listen to the input. Unfortunately, I am stuck with trying to implement the latter, since I need to attach to the currently running console, not create a new port to some other process (as it is perfectly described here.)
Am I missing something obvious on how am I to attach a Port
to the current process’ :stdin
(which is btw listed in Port.list/0
,) or should I’ve built the whole 3-piped architecture to redirect what’s typed to :stdin
and whatever my program wants to puts
to :stdout
?
Your program doesn't get the keys because on Linux, the terminal is by default in cooked mode, which buffers all keypresses until Return is pressed.
You need to switch your terminal to raw mode, which sends the keypresses to the application as soon as they happen. There's no cross-platform to do this.
For unix-like systems there's ncurses, which has an elixir binding that you should check out: https://github.com/jfreeze/ex_ncurses. It even has an example to do what you want.
The simplest thing I could cook up is based on this github repo. So you need the following:
reader.c
compile it with
gcc -o reader.so -fpic -shared reader.c
. Then you'll need inreader.erl
Compile it with
erlc reader.erl
.Then in iex
:reader.start(); :reader.read()
it issues a warning thatstdin
has been hijacked, and for every keypress you get Input received. The only problem is that when you press q the server terminates, but thestdin
is not accessible.