Espere asincrónicamente la salida de un proceso de impresión


12

En primer lugar, un descargo de responsabilidad. He investigado esto muchas veces, y estoy bastante seguro de que ya he encontrado la respuesta de una forma u otra, pero simplemente no la entiendo.

Mi problema es el siguiente:

  • Tengo un proceso que se ejecuta a través de comint
  • Quiero enviar una línea de entrada, capturar salida y ver cuándo termina (cuando la última línea de la salida coincide con la expresión regular para una solicitud)
  • solo cuando el proceso haya terminado de enviar la salida, quiero enviar otra línea de entrada (por ejemplo).

Para un poco de antecedentes, piense en un modo principal que implementa la interacción con un programa, que puede devolver una cantidad arbitraria de salida, en un tiempo arbitrariamente largo. Esto no debería ser una situación inusual, ¿verdad? De acuerdo, tal vez la parte donde necesito esperar entre entradas es inusual, pero tiene algunas ventajas sobre el envío de la entrada en su conjunto:

  • el búfer de salida está bien formateado: entrada salida entrada salida ...
  • lo que es más importante, cuando se envía mucho texto a un proceso, el texto se corta en pedazos y el proceso los vuelve a pegar; los puntos de corte son arbitrarios y esto a veces hace una entrada no válida (mi proceso no pegará correctamente un corte de entrada en el medio de un identificador, por ejemplo)

De todos modos, inusual o no, resulta que es complicado. En este momento, estoy usando algo en la línea de

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

y lo llamo cada vez que envío una línea de entrada y antes de enviar la siguiente. Bueno ... funciona, eso ya es algo.

Pero también hace que emacs se cuelgue mientras espera la salida. La razón es obvia, y pensé que si incluía algún tipo de asíncrono sleep-for(por ejemplo, 1s) en el bucle, retrasaría la salida en 1s, pero suprimiría el bloqueo. Excepto que parece que este tipo de asíncrono sleep-for no existe .

O lo hace? En términos más generales, ¿hay una forma idiomática de lograr esto con emacs? En otras palabras:

¿Cómo enviar entradas a un proceso, esperar la salida y luego enviar más entradas de forma asincrónica?

Al buscar alrededor (ver las preguntas relacionadas), principalmente he visto menciones de centinelas (pero no creo que se aplique en mi caso, ya que el proceso no termina) y de algunos ganchos de comint (pero ¿qué? haga que el gancho sea localmente búfer, convierta mi "evaluar las líneas restantes" en una función, agregue esta función al gancho y limpie el gancho después (eso suena muy sucio, ¿no?).

Lo siento si no me estoy aclarando, o si realmente hay una respuesta obvia disponible en alguna parte, estoy realmente confundido por todas las complejidades de la interacción del proceso.

Si es necesario, puedo hacer de todo esto un ejemplo de trabajo, pero me temo que solo haría una "pregunta de proceso específica con una respuesta de proceso específica" como todas las que encontré anteriormente y que no me ayudaron.

Algunas preguntas relacionadas sobre SO:


@nicael ¿Qué hay de malo en los enlaces relacionados?
T. Verron

Pero, ¿por qué necesitas incluirlos?
nicael

2
Bueno, he descubierto que son preguntas relacionadas, incluso si las respuestas no me ayudaron. Si alguien quiere ayudarme, presumiblemente tendrán un conocimiento más profundo del asunto que yo, pero tal vez aún tengan que realizar una investigación de antemano. En este caso, las preguntas les dan un punto de partida. Y además, si algún día alguien llega a esta página pero con un problema más parecido a los que he vinculado, tendrán un acceso directo a la pregunta correspondiente.
T. Verron

@nicael (Olvidé hacer ping en la primera publicación, lo siento) ¿Es un problema que los enlaces no sean de mx.sx?
T. Verron

Okay. Puede volver a su revisión, es su publicación.
nicael

Respuestas:


19

En primer lugar, no debe usarlo accept-process-outputsi desea un procesamiento asincrónico. Emacs aceptará la salida cada vez que esté esperando la entrada del usuario.

La forma correcta de hacerlo es usar funciones de filtro para interceptar la salida. No necesita crear o eliminar los filtros dependiendo de si todavía tiene líneas para enviar. Por el contrario, generalmente declarará un solo filtro durante la vida útil del proceso y utilizará variables locales de búfer para rastrear el estado y hacer diferentes cosas según sea necesario.

La interfaz de bajo nivel.

Las funciones de filtro son lo que estás buscando. Las funciones de filtro son la salida de lo que los centinelas son a la terminación.

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

Mira el manual o en muchos ejemplos que vienen con Emacs ( grepde process-filterlos .elarchivos).

Registre su función de filtro con

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

La interfaz de comint

Comint define una función de filtro que hace algunas cosas:

  • Cambie al búfer que debe contener la salida del proceso.
  • Ejecute las funciones en la lista comint-preoutput-filter-functions, pasándoles el nuevo texto como argumento.
  • Realice alguna eliminación puntual de duplicados ad hoc, según comint-prompt-regexp.
  • Inserte la salida del proceso al final del búfer
  • Ejecute las funciones en la lista comint-output-filter-functions, pasándoles el nuevo texto como argumento.

Dado que su modo se basa en comint, debe registrar su filtro comint-output-filter-functions. Debe configurar comint-prompt-regexppara que coincida con su solicitud. No creo que Comint tenga una función incorporada para detectar un fragmento de salida completo (es decir, entre dos avisos), pero puede ayudar. El marcador comint-last-input-endse establece al final del último fragmento de entrada. Tiene un nuevo fragmento de salida cuando finaliza la última solicitud comint-last-input-end. Cómo encontrar el final de la última solicitud depende de la versión de Emacs:

  • Hasta 24.3, la superposición comint-last-prompt-overlayabarca la última solicitud.
  • Desde 24.4, la variable comint-last-promptcontiene marcadores al inicio y al final de la última solicitud.
(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)))
      ...)))

Es posible que desee agregar protecciones en caso de que el proceso emita resultados en una secuencia que no sea {recibir entrada, emitir salida, mostrar mensaje}.


La variable comint-last-prompt-overlay no parece estar definida en Emacs 25 en comint.el. ¿Es de otro lado?
John Kitchin

@JohnKitchin Esa parte de la queja cambió en 24.4, había escrito mi respuesta para 24.3. He agregado un método posterior a 24.4.
Gilles 'SO- deja de ser malvado'
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.