如何捕获所有需要用户交互的 elisp 函数调用?

发布于 2024-09-12 13:30:29 字数 1995 浏览 8 评论 0原文

我想编写一个函数来调用 emacs 中的函数列表(具体来说,kill-buffer-query-functions),但如果其中任何一个需要用户交互,我想让它们简单地返回相反,nil,这样整个过程将以非交互方式运行。我正在考虑使用 defadvice 来修改通常会提示用户抛出值为 nil 的异常的每个函数。然后我将用 catch 表单包装所有内容。唯一的麻烦是,我没有可能提示用户输入某些内容的所有 emacs elisp 函数的详尽列表。

有谁有这样的列表,或者有更简单的方法吗?例如,函数 yes-or-no-py-or-np 肯定会出现在这个列表中,read-string 也会出现在这个列表中> 和完成读取

基本上,我想填写此代码中的省略号:

(defun eval-but-return-if-requires-user (form &optional interactive-return)
  "Evaluate FORM, but immediately stop and return INTERACTIVE-RETURN if any part of FORM would require user interaction."
  ;; Set up the safety net
  (catch 'ui-required 
    (let ((ui-requiring-functions '(  ...  )) ; What goes in this list?
          (ui-required-callback 
           ;; A function that takes any number of args and throws an exception
           '(lambda (&rest args) 
              (throw 'ui-required interactive-return)))
          (flet-redefinitions
           ;; Use the above callback to create a list of redefinitions for `flet'
           (mapcar (lambda (func) 
                     (cons func (cdr ui-required-callback))) 
                   ui-requiring-functions)))
      `(flet 
           ,flet-redefinitions          ; Perform the function redefinitions,
         ,form))))                      ; Then evaluate FORM

基本上,我将所有内容包装在 catch 块中,然后定义一个函数体,该函数体将简单地接受任何参数并抛出适当的异常。我设置了一个要传递给 flet 的重定义列表,它将临时重新定义这些函数以使用前面提到的异常抛出主体。最后,我使用这些临时函数重新定义来评估 form

现在,该代码中可能存在一些引用错误,但我认为如果我有要重新定义的函数的适当列表,它就会起作用。

请注意,如果需要任何用户交互,我希望返回整个表单,而不仅仅是表单中需要用户交互的特定函数调用。要了解为什么我需要这个,请考虑该表单可能想要询问以下任一问题:

  • 您想删除这个非常重要的文件吗?是或否
  • 您想保留这个非常重要的文件吗?是或否

显然,如果我只是修改了 yes-or-no-p 来始终返回 nil (这意味着“否”),仍然不能保证保存我的重要文件免遭删除。因此,由于我不知道可能会问什么问题,所以如果它想询问用户任何问题,我想让整个表单简单地中止并返回特定值。

本质上,我想说“评估这段代码,但如果你必须问我任何问题才能这样做,那就忘记它并返回 nil 。”

I want to write a function that will call a list of functions in emacs (specifically, kill-buffer-query-functions), but if any of them require user interaction, I want to have them simply return nil instead, so that the whole thing will run non-interactively. I am thinking of using defadvice to modify every function that would normally prompts the user to instead throw an exception with a value of nil. Then I will wrap everything with a catch form. The only trouble is, I don't have an exhaustive list of all emacs elisp functions that might prompt the user for something.

Does anyone have such a list, or is there an easier way to go about this? As examples, the functions yes-or-no-p and y-or-n-p would certainly be on this list, as would read-string and completing-read.

Basically, I want to fill in the ellipsis in this code:

(defun eval-but-return-if-requires-user (form &optional interactive-return)
  "Evaluate FORM, but immediately stop and return INTERACTIVE-RETURN if any part of FORM would require user interaction."
  ;; Set up the safety net
  (catch 'ui-required 
    (let ((ui-requiring-functions '(  ...  )) ; What goes in this list?
          (ui-required-callback 
           ;; A function that takes any number of args and throws an exception
           '(lambda (&rest args) 
              (throw 'ui-required interactive-return)))
          (flet-redefinitions
           ;; Use the above callback to create a list of redefinitions for `flet'
           (mapcar (lambda (func) 
                     (cons func (cdr ui-required-callback))) 
                   ui-requiring-functions)))
      `(flet 
           ,flet-redefinitions          ; Perform the function redefinitions,
         ,form))))                      ; Then evaluate FORM

Basically, I wrap everything in a catch block, then I define a function body that will simply take any arguments and throw an appropriate exception. I set up a list of redefinitions to be passed to flet, which will temporarily redefine those functions to use the aforementioned exception-throwing body. Finally, I evaluate form with those temporary function redefinitions in place.

Now, there's probably some quoting errors in that code, but I think that it would work if I just had the appropriate list of which functions to redefine.

Note that I want the entire form to return if any user interaction is required, not just the particular function call within the form that required user interaction. To see why I need this, consider that the form could possibly want to ask either of the following questions:

  • Do you want to delete this very important file? yes or no
  • Do you want to keep this very important file? yes or no

Obviously, if I just modified yes-or-no-p to always return nil (which means "no"), that still isn't guaranteed to save my important file from deletion. So, since I don't know what questions might be asked, I want to cause the entire form to simply abort and return a specific value if it wants to ask anything of the user.

Essentially, I want to say "Evaluate this code, but if you have to ask me anything in order to do so, just forget about it and return nil."

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

软的没边 2024-09-19 13:30:29

我认为你可以使用“commandp”来判断特定功能是否是交互式的。

所以只需运行 (remove-if 'commandp Kill-buffer-query-functions)

I think you can just use "commandp" to tell whether a particular function is interactive.

So just run (remove-if 'commandp kill-buffer-query-functions)

简单爱 2024-09-19 13:30:29

我刚刚偶然发现了一个可能的答案:

Mx describe-function minibuffer-with-setup-hook

(带设置挂钩的迷你缓冲区乐趣和休息
身体)

将 FUN 添加到“minibuffer-setup-hook”
执行 BODY 时。身体应该使用
迷你缓冲区最多一次。递归
迷你缓冲区的使用不会
受影响。

因此,使用它,如果我想评估 BODY 但如果 BODY 尝试使用迷你缓冲区,则中止并返回 nil,也许我可以使用这个:

(catch 'tried-to-use-minibuffer
  (minibuffer-with-setup-hook 
      (lambda (&rest args) (throw 'tried-to-use-minibuffer nil))
    BODY))

稍后我会测试一下。


后来的事了,我已经测试过了。效果很好。这是我的最终代码:

(defmacro without-minibuffer (&rest body)
  "Like `progn', but stop and return nil if any of BODY forms tries to use the minibuffer.

Also disable dialogs while evaluating BODY forms, since dialogs
are just an alternative to the minibuffer."
  `(catch 'tried-to-use-minibuffer
     (minibuffer-with-setup-hook
         (lambda (&rest args) (throw 'tried-to-use-minibuffer nil))
       (let ((use-dialog-box))          ; No cheating by using dialogs instead of minibuffer
         ,@body))))

;; This should be indented like progn
(put 'without-minibuffer 'lisp-indent-function (get 'progn 'lisp-indent-function))

I just stumbled upon a possible answer to this:

M-x describe-function minibuffer-with-setup-hook

(minibuffer-with-setup-hook FUN &rest
BODY)

Add FUN to `minibuffer-setup-hook'
while executing BODY. BODY should use
the minibuffer at most once. Recursive
uses of the minibuffer will not be
affected.

So, using that, if I wanted to evaluate BODY but abort and return nil if BODY tries to use the minibuffer, maybe I could use this:

(catch 'tried-to-use-minibuffer
  (minibuffer-with-setup-hook 
      (lambda (&rest args) (throw 'tried-to-use-minibuffer nil))
    BODY))

I will test this out later.


It's later, and I've tested this out. It works great. Here's my final code for this:

(defmacro without-minibuffer (&rest body)
  "Like `progn', but stop and return nil if any of BODY forms tries to use the minibuffer.

Also disable dialogs while evaluating BODY forms, since dialogs
are just an alternative to the minibuffer."
  `(catch 'tried-to-use-minibuffer
     (minibuffer-with-setup-hook
         (lambda (&rest args) (throw 'tried-to-use-minibuffer nil))
       (let ((use-dialog-box))          ; No cheating by using dialogs instead of minibuffer
         ,@body))))

;; This should be indented like progn
(put 'without-minibuffer 'lisp-indent-function (get 'progn 'lisp-indent-function))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文