Warten Sie asynchron auf die Ausgabe eines Comint-Prozesses

12

Zunächst ein Haftungsausschluss. Ich habe dies oft recherchiert und bin mir ziemlich sicher, dass ich die Antwort auf die eine oder andere Weise schon gefunden habe, aber ich verstehe es einfach nicht.

Mein Problem ist folgendes:

  • Ich habe einen Prozess, der durch comint läuft
  • Ich möchte eine Eingabezeile senden, die Ausgabe erfassen und sehen, wann sie beendet ist (wenn die letzte Zeile der Ausgabe mit dem regulären Ausdruck für eine Eingabeaufforderung übereinstimmt).
  • Erst wenn der Vorgang das Senden der Ausgabe beendet hat, möchte ich eine weitere Eingabezeile senden (zum Beispiel).

Stellen Sie sich für ein wenig Hintergrund einen Hauptmodus vor, der die Interaktion mit einem Programm implementiert, das in beliebig langer Zeit eine beliebige Menge an Ausgabe zurückgeben kann. Dies sollte keine ungewöhnliche Situation sein, oder? Okay, vielleicht ist der Teil, in dem ich zwischen den Eingaben warten muss, ungewöhnlich, aber es hat ein paar Vorteile gegenüber dem Senden der Eingabe als Ganzes:

  • Der Ausgabepuffer ist schön formatiert: Eingabe Ausgabe Eingabe Ausgabe ...
  • Noch wichtiger ist, dass beim Senden einer großen Menge Text an einen Prozess der Text in Teile geschnitten und die Teile durch den Prozess wieder eingefügt werden. Die Schnittpunkte sind willkürlich und dies führt manchmal zu ungültigen Eingaben (mein Prozess fügt beispielsweise einen Eingabeschnitt in der Mitte eines Bezeichners nicht korrekt ein).

Wie auch immer, ungewöhnliche oder nicht, es stellt sich heraus , dass es ist kompliziert. Im Moment benutze ich etwas in der Art von

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

und ich rufe dies jedes Mal nach dem Senden einer Eingabezeile und vor dem Senden der nächsten Zeile auf. Nun ... es funktioniert, das ist schon etwas.

Aber es lässt auch Emacs hängen, während sie auf die Ausgabe warten. Der Grund liegt auf der Hand, und ich stellte mir vor, dass, wenn ich eine Art asynchroner sleep-forSchleife (z. B. 1s) einbinde, die Ausgabe um 1s verzögert wird, das Hängen jedoch unterdrückt wird. Abgesehen davon, dass es diese Art von Asynchronität anscheinend sleep-for nicht gibt .

Oder doch? Gibt es einen idiomatischen Weg, dies mit Emacs zu erreichen? Mit anderen Worten:

Wie kann ich Eingaben an einen Prozess senden, auf die Ausgabe warten und dann asynchron weitere Eingaben senden?

Beim Durchsuchen (siehe die zugehörigen Fragen) habe ich hauptsächlich Erwähnungen von Sentinels (aber ich glaube nicht, dass dies in meinem Fall zutrifft, da der Prozess nicht abgeschlossen ist) und von einigen Comint-Hooks (aber was soll ich dann tun?) Gesehen mache den Hook buffer-local, verwandle meine "restlichen Zeilen auswerten" in eine Funktion, füge diese Funktion dem Hook hinzu und reinige den Hook danach - das klingt wirklich schmutzig, oder?).

Es tut mir leid, wenn ich mich nicht klar mache oder wenn irgendwo wirklich eine offensichtliche Antwort verfügbar ist, bin ich wirklich verwirrt über all die Komplikationen der Prozessinteraktion.

Bei Bedarf kann ich das alles zu einem funktionierenden Beispiel machen, aber ich befürchte, dass es nur eine weitere "spezifische Prozessfrage mit spezifischer Prozessantwort" geben wird, wie all jene, die ich zuvor gefunden habe und die mir nicht geholfen haben.

Einige verwandte Fragen zu SO:

T. Verron
quelle
@nicael Was ist falsch an den verwandten Links?
T. Verron
Aber warum müssen Sie sie einbeziehen?
Nicael
1
Nun, ich habe festgestellt, dass es sich um verwandte Fragen handelt, auch wenn mir die Antworten nicht geholfen haben. Wenn jemand mir helfen will, hat er vermutlich ein tieferes Wissen über die Angelegenheit als ich, aber vielleicht muss er vorher noch Nachforschungen anstellen. In diesem Fall geben die Fragen einen Ausgangspunkt. Und wenn eines Tages jemand auf dieser Seite landet, aber ein ähnliches Problem wie das, mit dem ich verbunden bin, hat er eine Verknüpfung zu der entsprechenden Frage.
T. Verron
@nicael (Ich habe vergessen, im ersten Beitrag zu pingen, sorry) Ist es ein Problem, dass die Links nicht von mx.sx stammen?
T. Verron
In Ordnung. Sie können zu Ihrer Revision zurückkehren, es ist Ihr Beitrag.
Nicael

Antworten:

19

Zunächst sollten Sie nicht verwenden, accept-process-outputwenn Sie eine asynchrone Verarbeitung wünschen. Emacs akzeptiert die Ausgabe jedes Mal, wenn es auf Benutzereingaben wartet.

Der richtige Weg besteht darin, Filterfunktionen zum Abfangen der Ausgabe zu verwenden. Sie müssen die Filter nicht erstellen oder löschen, je nachdem, ob Sie noch zu sendende Zeilen haben. Stattdessen deklarieren Sie normalerweise einen einzelnen Filter für die Lebensdauer des Prozesses und verwenden pufferlokale Variablen, um den Status zu verfolgen und je nach Bedarf verschiedene Aktionen auszuführen.

Die Low-Level-Schnittstelle

Filterfunktionen sind genau das, wonach Sie suchen. Filterfunktionen sollen ausgeben, was Sentinels zur Terminierung sind.

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

Schauen Sie sich das Handbuch oder an vielen Beispielen , die mit Emacs kommen ( grepfür process-filterin den .elDateien).

Registrieren Sie Ihre Filterfunktion mit

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

Die Comint-Schnittstelle

Comint definiert eine Filterfunktion, die einige Dinge tut:

  • Wechseln Sie zu dem Puffer, der die Prozessausgabe enthalten soll.
  • Führen Sie die Funktionen in der Liste aus comint-preoutput-filter-functionsund übergeben Sie ihnen den neuen Text als Argument.
  • Führen Sie einige Ad-hoc-Duplikate aus, basierend auf comint-prompt-regexp.
  • Fügen Sie die Prozessausgabe am Ende des Puffers ein
  • Führen Sie die Funktionen in der Liste aus comint-output-filter-functionsund übergeben Sie ihnen den neuen Text als Argument.

Da Ihr Modus auf comint basiert, sollten Sie Ihren Filter in registrieren comint-output-filter-functions. Sie sollten festlegen comint-prompt-regexp, dass sie Ihrer Eingabeaufforderung entsprechen. Ich glaube nicht, dass Comint über eine integrierte Funktion verfügt, mit der ein vollständiger Ausgabeabschnitt (dh zwischen zwei Eingabeaufforderungen) erkannt werden kann, aber dies kann Abhilfe schaffen. Der Marker comint-last-input-endwird am Ende des letzten Eingabeabschnitts gesetzt. Sie haben einen neuen Ausgabeabschnitt, wenn das Ende der letzten Eingabeaufforderung nach liegt comint-last-input-end. Wie Sie das Ende der letzten Eingabeaufforderung finden, hängt von der Emacs-Version ab:

  • Bis 24.3 erstreckt sich das Overlay comint-last-prompt-overlayüber die letzte Eingabeaufforderung.
  • Seit 24.4 comint-last-promptenthält die Variable Markierungen am Anfang und Ende der letzten Eingabeaufforderung.
(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)))
      ...)))

Sie können Schutzmechanismen hinzufügen, wenn der Prozess die Ausgabe in einer anderen Reihenfolge als {Eingaben empfangen, Ausgaben ausgeben, Eingabeaufforderung anzeigen} ausgibt.

Gilles 'SO - hör auf böse zu sein'
quelle
Die Variable comint-last-prompt-overlay scheint in Emacs 25 in comint.el nicht definiert zu sein. Ist es von woanders?
John Kitchin
@JohnKitchin Dieser Teil von comint hat sich in 24.4 geändert. Ich habe meine Antwort für 24.3 geschrieben. Ich habe eine Post-24.4-Methode hinzugefügt.
Gilles 'SO- hör auf böse zu sein'