Асинхронное ожидание вывода из процесса comint

12

Прежде всего, отказ от ответственности. Я исследовал это много раз, и я почти уверен, что я уже нашел ответ, так или иначе, но я просто не понимаю его.

Моя проблема заключается в следующем:

  • У меня процесс проходит через коминт
  • Я хочу отправить строку ввода, захватить вывод и посмотреть, когда он закончится (когда последняя строка вывода соответствует регулярному выражению для приглашения)
  • только когда процесс завершит отправку вывода, я хочу отправить еще одну строку ввода (например).

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

  • выходной буфер красиво отформатирован: ввод-вывод-вывод-вывод ...
  • что еще более важно, при отправке большого количества текста процессу, текст разрезается на части, а части вставляются процессом обратно; точки обрезки являются произвольными, и это иногда приводит к неправильному вводу (мой процесс, например, не будет правильно вставлять вырезку ввода в середине идентификатора)

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

(defun mymode--wait-for-output ()
  (let ((buffer (mymode-get-buffer)))
    (with-current-buffer buffer
      (goto-char (point-max))
      (forward-line 0)
      (while (not (mymode-looking-at-prompt))
        (accept-process-output nil 0.001)
        (redisplay)
        (goto-char (point-max))
        (forward-line 0))
      (end-of-line))))

и я вызываю это каждый раз после отправки строки ввода и перед отправкой следующей. Ну ... это работает, это уже что-то.

Но это также приводит к зависанию emacs в ожидании вывода. Причина очевидна, и я подумал, что если бы я включил sleep-forв цикл какой-то асинхронный (например, 1 с), он бы задержал вывод на 1 с, но подавил зависание. За исключением того, что кажется, что такого рода асинхронный sleep-for не существует .

Или это? В более общем смысле, есть ли идиоматический способ добиться этого с помощью emacs? Другими словами:

Как отправить входные данные процессу, дождаться вывода, а затем отправить дополнительные входные данные асинхронно?

При поиске (см. Связанные вопросы) я в основном видел упоминания о дозорных (но я не думаю, что это применимо в моем случае, так как процесс не завершается) и о некоторых ловушках для коминтов (но что тогда? сделайте ловушку буферной локальной, превратите мою «оценку оставшихся строк» ​​в функцию, добавьте эту функцию в ловушку и затем очистите ловушку - это звучит очень грязно, не так ли?).

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

При необходимости я могу сделать все это рабочим примером, но я боюсь, что это просто сделает еще один «конкретный вопрос процесса с конкретным ответом процесса», как все те, что я нашел ранее и не помог мне.

Некоторые связанные вопросы по SO:

Т. Веррон
источник
@nicael Что плохого в связанных ссылках?
Т. Веррон
Но зачем вам их включать?
Никаэль
2
Ну, я нашел их связанными вопросами, даже если ответы не помогли мне. Если кто-то захочет мне помочь, предположительно, он будет иметь более глубокие знания по этому вопросу, чем я, но, возможно, им все равно придется заранее провести исследование. В этом случае вопросы дают им некоторую отправную точку. И, кроме того, если когда-нибудь кто-нибудь попадет на эту страницу, но с проблемой, более похожей на ту, с которой я столкнулся, у него будет ярлык на соответствующий вопрос.
Т. Веррон
@nicael (забыл пинговать в первом посте, извините) Проблема в том, что ссылки не из mx.sx?
Т. Веррон
Ok. Вы можете вернуться к вашей ревизии, это ваш пост.
Никаэль

Ответы:

19

Прежде всего, вы не должны использовать, accept-process-outputесли вы хотите асинхронную обработку. Emacs будет принимать вывод каждый раз, когда ожидает ввода пользователя.

Правильный путь - использовать функции фильтра для перехвата вывода. Вам не нужно создавать или удалять фильтры в зависимости от того, есть ли еще строки для отправки. Скорее, вы будете обычно объявлять один фильтр на время жизни процесса и использовать локальные переменные буфера для отслеживания состояния и выполнения различных действий по мере необходимости.

Низкоуровневый интерфейс

Функции фильтра - это то, что вы ищете. Функции фильтра предназначены для вывода того, что часовые являются для завершения.

(defun mymode--output-filter (process string)
  (let ((buffer (process-buffer process)))
    (when (buffer-live-p buffer)
      (with-current-buffer buffer
        (goto-char (point-max))
        (forward-line 0)
        (when (mymode-looking-at-prompt)
          (do-something)
          (goto-char (point-max)))))))

Посмотрите на руководство или на многих примерах , которые приходят с Emacs ( grepдля process-filterв .elфайлах).

Зарегистрируйте свою функцию фильтра с

(set-process-filter 'mymode--output-filter)

Интерфейс Comint

Comint определяет функцию фильтра, которая делает несколько вещей:

  • Переключитесь на буфер, который должен содержать выходные данные процесса.
  • Запустите функции из списка comint-preoutput-filter-functions, передав им новый текст в качестве аргумента.
  • Выполните некоторую оперативную ликвидацию дубликатов, основываясь на comint-prompt-regexp.
  • Вставьте вывод процесса в конец буфера
  • Запустите функции из списка comint-output-filter-functions, передав им новый текст в качестве аргумента.

Учитывая, что ваш режим основан на comint, вы должны зарегистрировать свой фильтр в comint-output-filter-functions. Вы должны установить, comint-prompt-regexpчтобы соответствовать вашей подсказке. Я не думаю, что в Comint есть встроенное средство для обнаружения полного выходного блока (то есть между двумя запросами), но это может помочь. Маркер comint-last-input-endустанавливается в конце последнего входного блока. У вас есть новый выходной блок, когда конец последнего приглашения после comint-last-input-end. Как найти конец последнего приглашения зависит от версии Emacs:

  • До 24,3 оверлей comint-last-prompt-overlayохватывает последнюю подсказку.
  • Начиная с 24.4, переменная comint-last-promptсодержит маркеры для начала и конца последнего приглашения.
(defun mymode--comint-output-filter (string)
  (let ((start (marker-position comint-last-input-end))
        (end (if (boundp 'comint-last-prompt-overlay)
                 (and comint-last-prompt-overlay (overlay-start comint-last-prompt-overlay))
               (and comint-last-prompt (cdr comint-last-prompt))))
  (when (and start end (< start end))
    (let ((new-output-chunk (buffer-substring-no-properties start end)))
      ...)))

Вы можете добавить защиту в случае, если процесс выдает выходные данные в последовательности, отличной от {получать входные данные, выдавать выходные данные, отображать приглашение}.

Жиль "ТАК - прекрати быть злым"
источник
Кажется, переменная comint-last-prompt-overlay не определена в Emacs 25 в comint.el. Это откуда-то еще?
Джон Китчин
@JohnKitchin Эта часть жалобы изменилась в 24.4, я написал свой ответ для 24.3. Я добавил метод после 24.4.
Жиль "ТАК - перестань быть злым"