异步等待comint进程的输出


12

首先,免责声明。我已经研究了很多次,我很确定自己已经以一种或另一种方式找到了答案,但我只是不理解。

我的问题如下:

  • 我有一个通过comint运行的进程
  • 我想发送一行输入,捕获输出并查看结束时间(当输出的最后一行与提示的regexp匹配时)
  • 仅当进程完成输出发送后,我才想发送另一行输入(例如)。

作为一点背景知识,请考虑实现与程序交互的主要模式,该模式可能会在任意长时间内返回任意数量的输出。这不应该是异常情况,对吗?好的,也许我需要 输入之间等待的部分是不寻常的,但是与整体发送输入相比,它具有一些优势:

  • 输出缓冲区的格式很好:输入输出输入输出...
  • 更重要的是,当向流程发送大量文本时,该文本被切成小块,并被该流程粘贴回去;切割点是任意的,这有时会导致无效输入(例如,我的过程不会正确地将输入切割剪切回粘贴到标识符中间)

无论如何,无论是否异常,事实证明它都很复杂。现在,我正在使用类似

(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相关链接出了什么问题?
T. Verron 2014年

但是,为什么需要包括它们?
nicael 2014年

2
好吧,即使答案对我没有帮助,我也发现它们是相关的问题。如果有人想帮助我,大概他们会比我拥有更深入的了解,但是也许他们仍然需要事先进行研究。在这种情况下,问题为他们提供了一些起点。另外,如果某天某人登陆此页面,但遇到的问题与我链接的问题更相似,那么他们将找到相应问题的捷径。
T. Verron 2014年

@nicael(抱歉,在第一篇文章中ping不通,对不起),这些链接不是来自mx.sx吗?
T. Verron

好。您可以回滚到您的修订,即您的帖子。
nicael 2014年

Answers:


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设置在最后一个输入块的末尾。当最后一个提示的结尾是after之后,您将拥有一个新的输出块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似乎未在comint.el的Emacs 25中定义。是从其他地方来的吗?
约翰·基钦

@JohnKitchin comint的这一部分在24.4中进行了更改,我将答案写为24.3。我添加了24.4后的方法。
吉尔斯(Gilles)'所以
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.