I'm wondering if there is a way of capturing the absolute cursor position in command line from Elixir.
I know that I have to use the following ansi escape sequence \033[6n, and after executing:
echo -en "\033[6n"
prints exactly what I'm looking for, but I'm not sure how to get the command response from Elixir.
Thanks!
This one nearly drove me mad, I had to dig so many threads that I can't tell. I will add all the threads that are relevant to the solution, they are all worth a read.
So first thing first, we can't use System.cmd
, System.cmd is run without a tty
iex(1)> System.cmd("tty", [])
{"not a tty\n", 1}
What we are trying to do requires a TTY. So there are few interesting libraries for the same
https://github.com/alco/porcelain
But that also doesn't work with a TTY
iex(1)> Porcelain.shell("tty")
%Porcelain.Result{err: nil, out: "not a tty\n", status: 1}
Then came to another library
https://github.com/aleandros/shell_stream
This one seems to share the TTY
iex(3)> ShellStream.shell("tty") |> Enum.to_list
["/dev/pts/6"]
This TTY is the same as the TTY of the current terminal, which means the TTY is propagating to the child process
Next was to check if we could get the coordinates
iex(8)> ShellStream.shell("echo -en '033[6n'") |> Enum.to_list
[]
So after lots of hit and trial I came up with a approach
defmodule CursorPos do
def get_pos do
settings = ShellStream.shell("stty -g") |> Enum.to_list
#ShellStream.shell("stty -echo -echoctl -imaxbel -isig -icanon min 1 time 0")
ShellStream.shell("stty raw -echo")
#settings |> IO.inspect
spawn(fn ->
IO.write "\e[6n"
#ShellStream.shell "echo -en \"\033[6n\" > `tty`"
:timer.sleep(50)
IO.write "\n"
end)
io = IO.stream(:stdio,1)
data = io |> Stream.take_while(&(&1 != "R"))
data|> Enum.join |> IO.inspect
ShellStream.shell("stty #{settings}")
end
def main(args) do
get_pos
end
end
This kind of works but still needs you to press enter to read stdio
$ ./cursorpos
^[[24;1R
"\e[24;1"
It also alters the screen coordinates to get them, which is not what one would like. But the issue is that the coordinate control characters needs to be processed by your shell and not the child shell. My attempt to use
ShellStream.shell("stty -echo -echoctl -imaxbel -isig -icanon min 1 time 0")
Doesn't work as the stty
is not affecting the parent shell where we need to get the coordinates. So next possible solution is to do below
$ EXISTING=$(stty -g);stty -echo -echonl -imaxbel -isig -icanon min 1 time 0; ./cursorpos ; stty $EXISTING
"\e[24;1"
This works because we are able to alter the properties of the current tty. Now you may want to dig deeper and find how you could do that from inside the code.
I have put all my code into below Github project
https://github.com/tarunlalwani/elixir-get-current-cursor-position
Also you should look at below project
https://github.com/henrik/progress_bar
If your getting cursor position is not important then you can fix the cursor yourself to some position.
References
https://unix.stackexchange.com/questions/88296/get-vertical-cursor-position
How to get the cursor position in bash?
https://groups.google.com/forum/#!topic/elixir-lang-talk/9B1oe3KgjnE
https://hexdocs.pm/elixir/IO.html#getn/3
https://unix.stackexchange.com/questions/264920/why-doesnt-the-enter-key-send-eol
http://man7.org/linux/man-pages/man1/stty.1.html
https://github.com/jfreeze/ex_ncurses
How can I get the cursor's position in an ANSI terminal?
Get console user input as typed, char by char
I know it's weird macro stuff but it works! ;) I have discovered it when I was investigating an implementation of IO.ANSI.Sequence.home() function here.
Special thanks to one and only Thijs
defmodule Foo do
import IO.ANSI.Sequence
IO.ANSI.Sequence.defsequence :bar, 6, "n"
end
and then simply call:
IO.puts Foo.bar
The closest thing I've managed to achieve was this line:
~C(bash -c "echo -en '\033[6n'") |> :os.cmd
However, it returns '\e[6n'
instead of cursor position. Must be something with escaping symbols inside :os.cmd/1
function, don't know for sure.
Though it's not a complete answer, hope it helps anyway.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With