Получить позицию курсора в командной строке (Elixir)

Мне интересно, есть ли способ захвата абсолютной позиции курсора в командной строке от Elixir.

Я знаю, что мне нужно использовать следующую escape-последовательность ansi\033 [6n, и после выполнения:

echo -en "\033[6n" 

печатает именно то, что я ищу, но я не уверен, как получить ответ команды от Elixir.

Спасибо!

Ответ 1

Этот чуть не сбил меня с ума, мне пришлось вырыть столько нитей, которые я не могу сказать. Я добавлю все потоки, имеющие отношение к решению, все они заслуживают чтения.

Итак, во-первых, мы не можем использовать System.cmd, System.cmd запускается без tty

iex(1)> System.cmd("tty", [])
{"not a tty\n", 1}

То, что мы пытаемся сделать, требует TTY. Таким образом, есть несколько интересных библиотек для одного и того же

https://github.com/alco/porcelain

Но это также не работает с TTY

iex(1)> Porcelain.shell("tty")
%Porcelain.Result{err: nil, out: "not a tty\n", status: 1}

Затем перешел в другую библиотеку

https://github.com/aleandros/shell_stream

Кажется, что он разделяет TTY

iex(3)> ShellStream.shell("tty") |> Enum.to_list
["/dev/pts/6"]

Этот TTY совпадает с TTY текущего терминала, что означает, что TTY распространяется на дочерний процесс

Далее нужно было проверить, можем ли мы получить координаты

iex(8)> ShellStream.shell("echo -en '033[6n'") |> Enum.to_list
[]

Итак, после многих хитов и испытаний я придумал подход

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

Этот вид работ, но вам все еще нужно нажать enter, чтобы читать stdio

$ ./cursorpos
^[[24;1R

"\e[24;1"

Он также изменяет координаты экрана, чтобы получить их, что не то, что хотелось бы. Но проблема в том, что символы управления координатами должны обрабатываться вашей оболочкой, а не дочерней оболочкой. Моя попытка использовать

ShellStream.shell("stty -echo -echoctl -imaxbel -isig -icanon min 1 time 0")

Не работает, поскольку stty не влияет на родительскую оболочку, где нам нужно получить координаты. Итак, следующее возможное решение - сделать ниже

$ EXISTING=$(stty -g);stty -echo -echonl -imaxbel -isig -icanon min 1 time 0; ./cursorpos ; stty $EXISTING
"\e[24;1"

Это работает, потому что мы можем изменять свойства текущего tty. Теперь вы можете захотеть углубиться и найти, как вы можете это сделать изнутри кода.

Я поставил весь свой код ниже проекта Github

https://github.com/tarunlalwani/elixir-get-current-cursor-position

Также вы должны посмотреть ниже проект

https://github.com/henrik/progress_bar

Если ваша позиция курсора не важна, вы можете самостоятельно зафиксировать курсор в некоторой позиции.

Ссылки

https://unix.stackexchange.com/questions/88296/get-vertical-cursor-position

Как получить позицию курсора в 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

Как получить позицию курсора в терминале ANSI?

Получить пользовательский ввод консоли, напечатанный, char на char

Ответ 2

Ближайшей вещью, которую мне удалось достичь, была следующая строка:

~C(bash -c "echo -en '\033[6n'") |> :os.cmd

Однако он возвращает '\e[6n' вместо позиции курсора. Должно быть что-то с экранирующими символами внутри :os.cmd/1, не знаю точно.

Хотя это не полный ответ, надеюсь, что это все равно поможет.

Ответ 3

Я знаю, что это странный макрос, но он работает!;) Я обнаружил это, когда я изучал реализацию функции IO.ANSI.Sequence.home() здесь.

Особая благодарность одному и только Thijs

defmodule Foo do                          
  import IO.ANSI.Sequence                   
  IO.ANSI.Sequence.defsequence :bar, 6, "n" 
end

а затем просто вызовите: IO.puts Foo.bar

Ответ 4

Если вы знаете системную команду, используйте Системный модуль, чтобы выполнить ее из Elixir