【问题标题】:How to catch all elisp function calls that require user interaction?如何捕获所有需要用户交互的 elisp 函数调用?
【发布时间】:2010-08-04 01:33:34
【问题描述】:

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

有没有人有这样的清单,或者有没有更简单的方法来解决这个问题?例如,yes-or-no-py-or-n-p 函数肯定会在此列表中,read-stringcompleting-read 也会在此列表中。

基本上,我想在这段代码中填写省略号:

(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。”

【问题讨论】:

    标签: try-catch elisp non-interactive


    【解决方案1】:

    我认为您可以只使用“commandp”来判断特定功能是否具有交互性。

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

    【讨论】:

    • 有一些问题。首先,并非所有标记为(interactive) 的函数实际上都需要用户输入。其次,我不只是想在kill-buffer-query-functions 中捕获交互式函数,我还想在评估每个kill-buffer-query-functions 时,在调用堆栈中的任何位置捕获对需要用户输入的函数的任何调用。我将更新我的答案以澄清这一点。
    【解决方案2】:

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

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

    (minibuffer-with-setup-hook FUN &rest 身体)

    为 `minibuffer-setup-hook' 添加 FUN 在执行 BODY 时。身体应该使用 minibuffer 最多一次。递归的 minibuffer 的使用不会 受影响。

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

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

    【讨论】:

    • 这似乎很好用。谁能想到任何不涉及小缓冲区或对话框的提示用户的合理方式?
    猜你喜欢
    • 2011-07-19
    • 2021-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多