Sinkron menunggu output dari proses comint

12

Pertama-tama, penafian. Saya telah meneliti ini berkali-kali, dan saya cukup yakin bahwa saya sudah menemukan jawabannya, tetapi saya tidak memahaminya.

Masalah saya adalah sebagai berikut:

  • Saya memiliki proses yang berjalan melalui comint
  • Saya ingin mengirim sederetan input, menangkap output dan melihat kapan usai (ketika baris terakhir dari output cocok dengan regexp untuk prompt)
  • hanya ketika prosesnya selesai mengirimkan output, saya ingin mengirim jalur input lain (misalnya).

Untuk sedikit latar belakang, pikirkan mode utama yang menerapkan interaksi dengan suatu program, yang dapat mengembalikan jumlah output yang sewenang-wenang, dalam waktu yang lama secara sewenang-wenang. Ini seharusnya bukan situasi yang tidak biasa, bukan? Oke, mungkin bagian di mana saya harus menunggu di antara input tidak biasa, tetapi memiliki beberapa keunggulan dibandingkan mengirim input secara keseluruhan:

  • buffer output diformat dengan baik: input output input output ...
  • yang lebih penting, ketika mengirim banyak teks ke suatu proses, teks tersebut dipotong-potong dan potongan-potongan tersebut ditempelkan kembali oleh proses; titik potong sewenang-wenang dan ini kadang-kadang membuat input tidak valid (misalnya, proses saya tidak akan menempelkan kembali dengan benar potongan input di tengah pengenal, misalnya)

Pokoknya, tidak biasa atau tidak, ternyata bahwa itu adalah rumit. Saat ini, saya menggunakan sesuatu di sepanjang baris

(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))))

dan saya menyebutnya setiap kali setelah mengirim saluran input, dan sebelum mengirim yang berikutnya. Yah ... itu berhasil, itu sudah sesuatu.

Tapi itu juga membuat emacs hang sambil menunggu output. Alasannya jelas, dan saya pikir jika saya memasukkan semacam asinkron sleep-for(misalnya 1s) dalam loop itu akan menunda output oleh 1s, tetapi menekan penggantinya. Kecuali bahwa tampaknya jenis sinkron sleep-for tidak ada .

Atau apakah itu? Secara umum, adakah cara idiomatis untuk mencapai hal ini dengan emacs? Dengan kata lain:

Bagaimana cara mengirim input ke suatu proses, menunggu output, kemudian mengirim lebih banyak input, secara tidak sinkron?

Ketika mencari di sekitar (lihat pertanyaan terkait), saya terutama melihat menyebutkan penjaga (tapi saya tidak berpikir itu berlaku dalam kasus saya, karena prosesnya tidak selesai) dan beberapa kait komint (tapi kemudian apa? Haruskah saya buat hook-buffer lokal, ubah "evaluasi sisa baris" saya menjadi fungsi, tambahkan fungsi ini ke hook dan bersihkan hook sesudahnya? kedengarannya sangat kotor, bukan?).

Saya minta maaf jika saya tidak membuat diri saya jelas, atau jika benar-benar ada jawaban yang jelas di suatu tempat, saya benar-benar bingung dengan semua kerumitan interaksi proses.

Jika perlu, saya bisa menjadikan ini semua contoh yang baik, tetapi saya khawatir itu hanya akan membuat satu lagi "pertanyaan proses spesifik dengan jawaban proses spesifik" seperti semua yang saya temukan sebelumnya dan tidak membantu saya.

Beberapa pertanyaan terkait pada SO:

T. Verron
sumber
@nicael Apa yang salah tentang tautan terkait?
T. Verron
Tetapi mengapa Anda perlu memasukkan mereka?
nicael
2
Yah, saya telah menemukan mereka untuk pertanyaan terkait, bahkan jika jawabannya tidak membantu saya. Jika seseorang ingin membantu saya, mungkin mereka akan memiliki pengetahuan yang lebih dalam tentang masalah ini daripada saya, tetapi mungkin mereka masih perlu melakukan penelitian sebelumnya. Dalam hal ini, pertanyaan memberi mereka beberapa titik awal. Dan juga, jika suatu hari seseorang mendarat di halaman ini tetapi dengan masalah yang lebih mirip dengan yang saya tautkan, mereka akan memiliki jalan pintas ke pertanyaan yang sesuai.
T. Verron
@nicael (Lupa ping di posting pertama, maaf) Apakah ini masalah karena tautannya bukan dari mx.sx?
T. Verron
Baik. Anda dapat kembali ke revisi Anda, itu adalah posting Anda.
nicael

Jawaban:

19

Pertama-tama, Anda tidak boleh menggunakan accept-process-outputjika Anda ingin pemrosesan asinkron. Emacs akan menerima output setiap kali menunggu input pengguna.

Cara yang tepat untuk pergi adalah menggunakan fungsi filter untuk mencegat output. Anda tidak perlu membuat atau menghapus filter tergantung pada apakah Anda masih memiliki jalur untuk dikirim. Sebaliknya, Anda biasanya akan mendeklarasikan filter tunggal untuk masa proses dan menggunakan variabel buffer-lokal untuk melacak negara dan melakukan hal-hal yang berbeda sesuai kebutuhan.

Antarmuka tingkat rendah

Fungsi filter adalah apa yang Anda cari. Fungsi-fungsi filter adalah untuk menampilkan apa yang dimaksud dengan terminasi.

(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)))))))

Lihatlah manual atau banyak contoh yang datang dengan Emacs ( grepuntuk process-filterdalam .elfile).

Daftarkan fungsi filter Anda dengan

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

Antarmuka comint

Comint mendefinisikan fungsi filter yang melakukan beberapa hal:

  • Beralih ke buffer yang seharusnya berisi output proses.
  • Jalankan fungsi dalam daftar comint-preoutput-filter-functions, berikan teks baru sebagai argumen.
  • Lakukan beberapa penghapusan prompt duplikat ad hoc, berdasarkan comint-prompt-regexp.
  • Masukkan output proses di akhir buffer
  • Jalankan fungsi dalam daftar comint-output-filter-functions, berikan teks baru sebagai argumen.

Karena mode Anda didasarkan pada comint, Anda harus mendaftarkan filter Anda comint-output-filter-functions. Anda harus mengatur comint-prompt-regexpagar sesuai dengan permintaan Anda. Saya tidak berpikir Comint memiliki fasilitas built-in untuk mendeteksi chunk output lengkap (yaitu antara dua prompt), tetapi dapat membantu. Marker comint-last-input-enddiatur di akhir potongan input terakhir. Anda memiliki potongan output baru ketika akhir dari prompt terakhir adalah setelah comint-last-input-end. Cara menemukan akhir dari prompt terakhir tergantung pada versi Emacs:

  • Hingga 24,3, hamparan comint-last-prompt-overlaymencakup permintaan terakhir.
  • Sejak 24.4, variabel comint-last-promptberisi penanda ke awal dan akhir dari prompt terakhir.
(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)))
      ...)))

Anda mungkin ingin menambahkan proteksi jika proses memancarkan output dalam urutan selain dari {menerima input, memancarkan output, menampilkan prompt}.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Variabel comint-last-prompt-overlay tampaknya tidak didefinisikan dalam Emacs 25 di comint.el. Apakah itu dari tempat lain?
John Kitchin
@ JohnKitchin Bagian komint itu berubah dalam 24,4, saya telah menulis jawaban saya untuk 24,3. Saya telah menambahkan metode post-24.4.
Gilles 'SO- stop being evil'