Создание идеального скринкаста работы с терминалом

2024-09-03

Недавно выложил свою реализацию lisp-интерпретатора написанного на PHP. И, чтобы можно было хотя бы как-то посмотреть его работу ничего не запуская, решил прикрепить в readme скринкаст работы в REPL.

Сразу покажу результат:

asciicast

В принципе, в win11, уже всё для этого есть, но вводить текст без запинок опечаток и с постоянной скоростью оказалось не очень-то и просто. "Нужно отдать это дело на откуп программе!" - первая мысль посетившая меня после нескольких неудачных попыток.

Я использую WSL c Ubuntu для разработки и, уж чего-чего, а в линуксе 100% есть или уже готовая программа или её можно собрать на коленке из bash-скриптов и палок того что найдём в /usr/bin.

Итак, мне нужно эмулировать ввод текста с клавиатуры. Недолгий поиск привёл меня к xdotool. Есть только одна проблема:

xdotool --type "hello"

ничего не выводит, а если спросить

echo $? # 0

т.е. всё хорошо.

Выяснилось, что буква "x" в её названии не просто так, утилита работает только с x11-приложениями. Stack Overflow подсказал её решение:

sudo apt install xterm

Благо WSL2 позволяет без проблем работать с GUI-приложениями линукса. Все дальнейшие эксперименты с xdotool, и сам скринкаст, я проводил в xterm.

Одна проблема решена, теперь надо как-то сделать так чтобы на экране появлялось много строк. xdotool позволяет написать для неё последовательность команд и передать её на вход файлом или из stdin.

cat scenario.txt | xdotool -
xdotool scenario.txt

Мне больше импонирует первый вариант, потому, что так можно будет генерировать сценарий "на лету".

Итак, вручную я вводил в терминал что-то вроде этого:

$ make repl
?> ; First, let's see some help information.
?> :help
?> ; Let's write some code.
?> (defn sum (n a)
?> ..(cond (= n 0)
# …
?> ; Let's exit the REPL
?> :q

Видно, что нужно сделать две вещи:

Для запуска программ в xdotool предлагают использовать такой синтаксис:

exec program_name arg_1 

Запуск программы занимает какое-то время, нужно подождать пока она запустится и продолжить ввод, иначе xdotool продолжит ввод не дожидаясь её готовности к работе:

sleep 1

Это заставит утилиту подождать 1 секунду.

Для ввода строк нам нужна такая команда:

type "some string"

Ещё нам нужен перевод на новую строку:

key Return

Таким образом, чтобы ввести команду в REPL нужно два вызова:

type "some string"
key Return

Также нужно учесть, что, по-умолчанию, задержка ввода символов 12 мс. Это очень мало, нужно чтобы было похоже на ввод человеком:

type --delay 200 "some string"

Ещё нужно дать время зрителю прочитать строку целиком:

key --delay 1000 Return

Итого получим:

type --delay 200 "some string"
key --delay 1000 Return

Отлично, теперь можно представить как будет выглядеть сценарий:

exec make repl
sleep 1
type --delay 200 "; First, let's see some help info"
key --delay 1000 Return
type --delay 200 ":help"
key --delay 1000 Return
type --delay 200 "; Let's write some code."
key --delay 1000 Return
type --delay 200 "(defn sum (n a)"
key --delay 1000 Return
type --delay 200 "(cond (= n 0)"
key --delay 1000 Return
…

Писать это всё вручную нет никакого желания, проще написать текст который нужно вводить, а сценарий сгенерировать программой. Т.е. исходный сценарий будет выглядеть как-то так:

exec make repl
sleep 1
; First, let's see some help info
:help
; Let's write some code.
(defn sum (n a)
(cond (= n 0)
…

Как видите, за исключением первых двух строк, заготовка ничем не отличается от того что я бы вводил руками.

Осталось написать генератор сценария для xdotool на основе текста:

#!/bin/bash

FILE=$1
SPEED=${2:-100}
DELAY=${3:-1000}

while IFS= read -r line; do
    if [[ $line == exec* ]] || [[ $line == sleep* ]]
    then
        echo $line
    else
        echo "type --delay $SPEED \"$line\""
        echo "key --delay $DELAY Return"
    fi
done < $FILE

Сам скрипт я закинул в gist. Предложения по улушению принимаются!

Помимо имени файла, я добавил два опциональных параметра:

  1. скорость ввода символов
  2. задержка перед нажатием Enter

Смотрим результат:

$ ./gen.sh scenario.txt
exec make repl
sleep 1
type --delay 100 "; First, let's see some help information."
key --delay 1000 Return
type --delay 100 ":help"
key --delay 1000 Return
...

На первый взгляд всё хорошо. Попробуем запустить:

$ ./gen.sh scenario.txt | xdotool -
; docker compose run --rm --user “1000:1000” app ./repl
 First, let's see some help infothe input device is not TTY
make: *** [Makefile:20: repl] Error 1
rmation.
:help
...

Хм, не сработало, чего-то не так с TTY у докера (как видно из вывода, команда make repl запускает докер). Попробуем по-другому:

$ ./gen.sh scenario.txt > scen.txt && xdotool scen.txt
docker compose run --rm --user "1000:1000" app ./repl

=====================================================
Наберите :help для просмотра списка доступных команд
=====================================================

?>; First, let's see some help information.
?>:help
...

Отлично! Правда мне не нравится необходимость создавать промежуточный файл, но работа выполняется, жить можно.

Осталось решить несколько проблем, с которыми, впрочем, можно жить:

  1. xdotool не позволяет написать в консоль make repl и продолжить ввод уже в его окно приглашения, нужно именно запустить его, т.е. вызвать exec make repl. Из-за этого первую строчку в скринкасте мне пришлось смонтировать (и это большой плюс asciinema! Если бы я писал видео, то не знаю сколько бы сил этот монтаж у меня отнял.)
  2. Если поставить задержку перед вводом Enter более 1 секунды , то xdotool будет "нажимать" его несколько раз
  3. Хотелось бы разобраться почему не работает cat scenario.txt | xdotool -, быстрый поиск по проблеме мне не помог

Если Вы знаете как обойти какую-либо из этих проблем или как-то, по-своему, решаете проблему идеального ввода текста для скринкаста, ну или просто Вам есть что сказать, приглашаю в комментарии! Так же у меня есть в телеграм-канал.

Комментарии

Комментарии на GitHub