Асинхронно чекайте виходу з процесу comint


12

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

Моя проблема полягає в наступному:

  • У мене процес, що проходить через comint
  • Я хочу надіслати рядок введення, зафіксувати висновок і побачити, коли він закінчився (коли останній рядок виведення відповідає збігу з регулярним виразом для запиту)
  • тільки коли процес закінчив надсилати вихід, я хочу надіслати ще один рядок введення (наприклад).

Трохи задумавшись, подумайте про основний режим, що реалізує взаємодію з програмою, яка може повернути довільну кількість результатів у будь-який час. Це не повинно бути незвичною ситуацією, правда? Гаразд, можливо, частина, де мені потрібно почекати між входами, є незвичною, але вона має кілька переваг перед надсиланням введення в цілому:

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

У всякому разі, незвичайний чи ні, то виходить, що це буде складно. Зараз я щось використовую за принципом

(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(наприклад, 1s) в цикл, він затримає вихід на 1s, але придушить висіння. За винятком того, що схоже, що такого роду асинхронності sleep-for не існує .

Або це? Загалом, чи є ідіоматичний спосіб досягти цього за допомогою emacs? Іншими словами:

Як надіслати вхід в процес, дочекатися виходу, а потім надіслати більше вводу, асинхронно?

Під час пошуку навколо (див. Відповідні запитання) я в основному бачив згадки дозорних (але я не думаю, що це застосовується в моєму випадку, оскільки процес не закінчується) та деяких гачок на коментарях (але що тоді робити? зробіть гачок буфера локальним, перетворіть мою "оцінку решти рядків" у функцію, додайте цю функцію до гака та очистіть гачок згодом? Це звучить дуже брудно, чи не так?).

Мені шкода, якщо я не даю зрозуміти себе, або якщо десь наявна очевидна відповідь, мене дуже бентежать усі тонкощі процесу взаємодії.

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

Деякі пов'язані питання щодо SO:


@nicael Що не так у пов'язаних посиланнях?
Т. Веррон

Але навіщо їх включати?
nicael

2
Ну, я виявив, що вони пов'язані між собою, навіть якщо відповіді не допомогли мені. Якщо хтось хоче мені допомогти, імовірно, він буде мати глибші знання з цього питання, ніж я, але, можливо, їм все одно потрібно буде заздалегідь провести дослідження. У цьому випадку питання дають їм певну вихідну точку. І крім того, якщо колись хтось приземлиться на цю сторінку, але з проблемою, що більше схожа на ті, з якими я пов’язаний, вони матимуть ярлик до відповідного питання.
Т. Веррон

@nicael (забув пінг у першому дописі, вибачте) Це проблема, що посилання не з mx.sx?
Т. Веррон

Гаразд. Ви можете повернутись до своєї редакції, до своєї публікації.
nicael

Відповіді:


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.
Жил "ТАК - перестань бути злим"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.