Как получить позицию курсора с помощью PHP-CLI? - PullRequest
2 голосов
/ 28 апреля 2019

С помощью PHP-скрипта, работающего в режиме CLI, я хочу получить позицию курсора переносимым способом.

С кодом:

// Query Cursor Position
echo "\033[6n";

В терминале этот код сообщает позицию курсора, как

wb ?> ./script.php 
^[[5;1R
wb ?> ;1R 

Но я не могу получить два значения (строка: 5, столбец: 1) в коде.

После некоторых тестов с буферизацией вывода:

ob_start();
echo "\033[6n";
$s = ob_get_contents();
file_put_contents('cpos.txt',$s);

В файле cpos.txt есть "\ 033 [6n", а не ответ устройства.

И читает STDIN:

$timeout = 2;
$sent = false;
$t = microtime(true);
$buf = '';
stream_set_blocking(STDIN,false);
while(true){
    $buf .= fread(STDIN,8);
    if(!$sent){
        echo "\033[6n";
        $sent = true;
    }
    if($t+$timeout<microtime(true))
        break;
}
var_dump($buf);

Буфер пуст, но терминал показывает ответ устройства:

wb ?> ./script.php 
^[[5;1R
string(0) ""
wb ?>

Есть ли способ, без проклятий, получить позицию курсора?

Ответы [ 2 ]

0 голосов
/ 29 апреля 2019

Код, который у вас есть, почти работает, и вы обнаружите, что нажатие клавиши ввода и ожидание завершения тайм-аута приводит к строке, содержащей ответ, но с символом \n в конце.(Обратите внимание, что длина строки 7 вместо 0.)

$ php foo.php
^[[2;1R                           
string(7) "
"

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

Чтобы терминал немедленно отправлял символы в вашу программу без буферизации строки, вам необходимо установить для терминала "неканонический" *Режим 1009 *.Это отключает любые функции редактирования строк, такие как возможность нажимать клавишу Backspace, чтобы стереть символы, и вместо этого немедленно отправляет символы во входной буфер.Самый простой способ сделать это в PHP - вызвать утилиту Unix stty.

<?php
system('stty -icanon');

echo "\033[6n";
$buf = fread(STDIN, 16);

var_dump($buf);

. Этот код успешно фиксирует ответ от терминала в $buf.

$ php foo.php
^[[2;1Rstring(6) ""

.Тем не менее, этот код имеет несколько проблем.Прежде всего, он не повторно включает канонический режим в терминале после его завершения.Это может вызвать проблемы при попытке ввода из stdin позже в вашей программе или в вашей оболочке после выхода из программы.Во-вторых, код ответа от терминала ^[[2;1R все еще отражается на терминале, что делает вывод вашей программы грязным, когда все, что вы хотите сделать, это прочитать это в переменную.

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

<?php
// Save terminal settings.
$ttyprops = trim(`stty -g`);

// Disable canonical input and disable echo.
system('stty -icanon -echo');

echo "\033[6n";
$buf = fread(STDIN, 16);

// Restore terminal settings.
system("stty '$ttyprops'");

var_dump($buf);

Сейчаспри запуске программы мы не видим никакой ненужной информации в терминале:

$ php foo.php 
string(6) ""

Последнее возможное улучшение, которое мы можем внести в это, - это позволить программе запускаться, когда stdout перенаправляется в другой процесс./ файл.Это может или не может быть необходимо для вашего приложения, но в настоящее время запуск php foo.php > /tmp/outfile не будет работать, так как echo "\033[6n"; будет записывать данные напрямую в выходной файл, а не в терминал, в результате чего ваша программа будет ожидать отправки символов в stdin.поскольку терминалу никогда не отправлялась какая-либо escape-последовательность, он не будет отвечать на него.Обходной путь для этого - написать в /dev/tty вместо стандартного вывода следующим образом:

$term = fopen('/dev/tty', 'w');
fwrite($term, "\033[6n");
fclose($term); // Flush and close the file.

Соберите все это вместе и используйте bin2hex() вместо var_dump(), чтобы получить список символов в $buf мы получаем следующее:

<?php
$ttyprops = trim(`stty -g`);
system('stty -icanon -echo');

$term = fopen('/dev/tty', 'w');
fwrite($term, "\033[6n");
fclose($term);

$buf = fread(STDIN, 16);

system("stty '$ttyprops'");

echo bin2hex($buf) . "\n";

Мы видим, что программа работает правильно следующим образом:

$ php foo.php > /tmp/outfile
$ cat /tmp/outfile
1b5b323b3152
$ xxd -p -r /tmp/outfile | xxd
00000000: 1b5b 323b 3152                           .[2;1R

Это показывает, что $buf содержит ^[[2;1R, указывая курсорнаходился в строке 2 и столбце 1, когда запрашивалась его позиция.

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

<?php
// Example response string.
$buf = "\033[123;456R";

$matches = [];
preg_match('/^\033\[(\d+);(\d+)R$/', $buf, $matches);

$row = intval($matches[1]);
$col = intval($matches[2]);
echo "Row: $row, Col: $col\n";

Это дает следующий вывод:

Row: 123, Col: 456

Стоит отметить, что весь этот код переносим только в Unix-подобные операционные системы и ANSI/ VT100-совместимые терминалы.Этот код может не работать в Windows, если вы не запустите программу под Cygwin / MSYS2.Я также рекомендую вам добавить к этому коду некоторую обработку ошибок на случай, если вы не получите ожидаемый от терминала ответ по какой-либо причине.

0 голосов
/ 28 апреля 2019

(это действительно комментарий, но он немного длинный)

Использование жестко закодированных терминальных последовательностей очень далеко от "портативного".Хотя большинство доступных в настоящее время эмуляторов терминалов будут поддерживать коды ANSI, vt100 или xterm, которые имеют общую базу, существует очень четко определенный API для доступа к интерактивным терминалам, известным как «проклятия».Расширение PHP доступно в pecl .Это просто заглушка интерфейса с системой curses, присутствующая в любой системе Unix / Linux.Хотя это можно настроить в mswindows, используя cygwin или pdcurses, это не так просто.Вы не упомянули, над какой ОС вы работаете.(Консоль mswindows использует последовательности ANSI )

Существует набор инструментов ( hoa ), основанный на termcap (предшественник проклятий), который может быть полезен.

Чтобы «извлечь» данные, которые вам просто нужно прочитать из стандартного ввода (хотя для этого целесообразно использовать неблокирование).

...