Attendere in modo asincrono l'output da un processo comint


12

Prima di tutto, un disclaimer. L'ho cercato molte volte e sono abbastanza sicuro di aver già trovato la risposta in un modo o nell'altro, ma non capisco.

Il mio problema è il seguente:

  • Ho un processo che passa attraverso Comint
  • Voglio inviare una riga di input, acquisire l'output e vedere quando è finita (quando l'ultima riga dell'output corrisponde al regexp per un prompt)
  • solo quando il processo ha terminato l'invio dell'output, voglio inviare un'altra riga di input (ad esempio).

Per un po 'di background, pensa a una modalità principale che implementa l'interazione con un programma, che può restituire una quantità arbitraria di output, in un tempo arbitrariamente lungo. Questa non dovrebbe essere una situazione insolita, giusto? Va bene, forse la parte in cui devo attendere tra gli input è insolita, ma presenta alcuni vantaggi rispetto all'invio dell'input nel suo insieme:

  • il buffer di output è ben formattato: input output input output ...
  • ancora più importante, quando si invia molto testo a un processo, il testo viene tagliato in pezzi e i pezzi vengono incollati indietro dal processo; i punti di taglio sono arbitrari e questo a volte rende l'input non valido (il mio processo non incollerà correttamente un taglio di input nel mezzo di un identificatore, per esempio)

Comunque, insolito o no, si scopre che è complicato. In questo momento, sto usando qualcosa sulla falsariga di

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

e lo chiamo ogni volta dopo aver inviato una linea di input e prima di inviare quella successiva. Bene ... funziona, è già qualcosa.

Ma fa anche emacs in attesa durante l'output. Il motivo è ovvio, e ho pensato che se avessi incluso un tipo di asincrono sleep-for(per esempio 1s) nel loop, questo avrebbe ritardato l'output di 1s, ma avrebbe soppresso il blocco. Solo che sembra che questo tipo di asincrono sleep-for non esista .

O lo fa? Più in generale, esiste un modo idiomatico per raggiungere questo obiettivo con Emacs? In altre parole:

Come inviare input a un processo, attendere l'output, quindi inviare più input, in modo asincrono?

Durante la ricerca (vedi le domande correlate), ho visto principalmente citazioni di sentinelle (ma non penso che si applichi nel mio caso, poiché il processo non termina) e di alcuni hook comint (ma allora cosa? rendere l'hook buffer locale, trasformare il mio "valutare le linee rimanenti" in una funzione, aggiungere questa funzione all'hook e pulire successivamente l'hook? che suona davvero sporco, vero?).

Mi dispiace se non mi sto chiarendo, o se c'è davvero una risposta ovvia disponibile da qualche parte, sono davvero confuso da tutti gli intricati dell'interazione di processo.

Se necessario, posso fare di tutto questo un esempio funzionante, ma temo che farebbe solo un'altra "domanda di processo specifica con una risposta di processo specifica" come tutte quelle che ho trovato prima e che non mi hanno aiutato.

Alcune domande correlate su SO:


@nicael Cosa c'è che non va nei link correlati?
T. Verron,

Ma perché è necessario includerli?
nicael,

2
Bene, le ho trovate come domande correlate, anche se le risposte non mi hanno aiutato. Se qualcuno vuole aiutarmi, presumibilmente avranno una conoscenza più approfondita della questione di me, ma forse dovranno comunque svolgere una ricerca in anticipo. In questo caso, le domande danno loro un punto di partenza. Inoltre, se un giorno qualcuno atterra su questa pagina ma con un problema più simile a quelli a cui mi sono collegato, avranno un collegamento alla domanda appropriata.
T. Verron,

@nicael (Hai dimenticato di eseguire il ping nel primo post, scusa) È un problema che i link non provengano da mx.sx?
T. Verron,

Ok. Puoi tornare alla tua revisione, è il tuo post.
nicael,

Risposte:


19

Prima di tutto, non dovresti utilizzare accept-process-outputse desideri l'elaborazione asincrona. Emacs accetterà l'output ogni volta che è in attesa dell'input dell'utente.

Il modo corretto di procedere è utilizzare le funzioni di filtro per intercettare l'output. Non è necessario creare o eliminare i filtri a seconda che siano ancora presenti righe da inviare. Piuttosto, in genere dichiarerai un singolo filtro per tutta la durata del processo e utilizzerai le variabili buffer-local per tenere traccia dello stato e fare diverse cose secondo necessità.

L'interfaccia di basso livello

Le funzioni di filtro sono ciò che stai cercando. Le funzioni di filtro consentono di visualizzare le sentinelle fino alla loro conclusione.

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

Guarda il manuale o molti esempi forniti con Emacs ( grepcome process-filternei .elfile).

Registra la tua funzione filtro con

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

L'interfaccia comint

Comint definisce una funzione di filtro che fa alcune cose:

  • Passare al buffer che dovrebbe contenere l'output del processo.
  • Esegui le funzioni nell'elenco comint-preoutput-filter-functions, passando loro il nuovo testo come argomento.
  • Eseguire una certa eliminazione del prompt duplicato ad hoc, in base a comint-prompt-regexp.
  • Inserire l'output del processo alla fine del buffer
  • Esegui le funzioni nell'elenco comint-output-filter-functions, passando loro il nuovo testo come argomento.

Dato che la modalità è basata su comint, è necessario registrare il filtro comint-output-filter-functions. È necessario impostare la comint-prompt-regexpcorrispondenza con il prompt. Non penso che Comint abbia una funzione integrata per rilevare un blocco di output completo (ovvero tra due prompt), ma può essere d'aiuto. Il marker comint-last-input-endè impostato alla fine dell'ultimo blocco di input. È disponibile un nuovo blocco di output dopo la fine dell'ultimo prompt comint-last-input-end. Come trovare la fine dell'ultimo prompt dipende dalla versione di Emacs:

  • Fino alla 24.3, l'overlay comint-last-prompt-overlaycopre l'ultimo prompt.
  • Dalla 24.4, la variabile comint-last-promptcontiene marcatori all'inizio e alla fine dell'ultimo 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)))
      ...)))

È possibile che si desideri aggiungere protezioni nel caso in cui il processo emetta output in una sequenza diversa da {ricevi input, emetti output, visualizza prompt}.


La variabile comint-last-prompt-overlay non sembra essere definita in Emacs 25 in comint.el. Viene da qualche altra parte?
John Kitchin,

@JohnKitchin Quella parte di Comint è cambiata in 24.4, avevo scritto la mia risposta per 24.3. Ho aggiunto un metodo post-24.4.
Gilles 'SO- smetti di essere malvagio' il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.