views:

28

answers:

2

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."

A: 

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)

offby1
A few problems with that. First not all functions marked as `(interactive)` actually require user input. Second, I don't just want to catch interactive functions within `kill-buffer-query-functions`, I want to catch any call to a function requiring user input anywhere in the call stack while evaluating the each of the `kill-buffer-query-functions`. I'll update my answer to clarify this.
Ryan Thompson
A: 

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))
Ryan Thompson
This seems to work great. Can anyone think of any reasonable way of prompting the user that doesn't involve either the minibuffer or a dialog box?
Ryan Thompson