6

Is there a way to lookup which hooks will run when a given command is called?

For example, for major modes it is pretty easy to guess that activating python-mode will run python-mode-hook. But sometimes it is not obvious (to me at least) which hooks are run.

I would like to know if there is a way to get a list of hooks that will run upon execution of a given command similar to how M-x describe-variable python-mode-hook will show me a list of functions called when that hook runs. Is this possible?

  • 1
    This almost sounds like a variation on the halting problem... – wasamasa Jan 16 '16 at 14:08
  • @wasamasa uh-oh, the key word in the Wikipedia article about the halting problem seems to be "undecidable" which doesn't bode well for my question... – elethan Jan 16 '16 at 14:23
  • Doubt there's something to do this. The problem is also that a command might invoke any number of functions that themselves run hooks. That could go several levels deep in principle. – Dan Jan 16 '16 at 15:34
  • @Dan, that's a good point, but really I am interested in the first level because I could use the same method to dive into the other levels (if such a method exists - which it sounds like it doesn't) – elethan Jan 16 '16 at 15:37
  • 1
    @KaushalModi Deciding before executing a program whether it will terminate. This one isn't as severe, but close, the very act of executing a command in Emacs will run hooks, if a command was defined with a macro running hooks (like nearly every major mode) these will be run as well and finally, any code of the command can run hooks or functions running hooks which can go arbitrarily deep. – wasamasa Jan 16 '16 at 17:13
5

I'm pretty sure it's impossible to know a priori what hooks a command will run; as others have mentioned, this smells a lot like the halting problem.

But it's certainly possible to actually execute a command and make Emacs tell you when it runs a hook. There is, as far as I know, no out-of-the-box command to do this, so I whipped one up.

(defun my/call-logging-hooks (command &optional verbose)
  "Call COMMAND, reporting every hook run in the process.
Interactively, prompt for a command to execute.

Return a list of the hooks run, in the order they were run.
Interactively, or with optional argument VERBOSE, also print a
message listing the hooks."
  (interactive "CCommand to log hooks: \np")
  (let* ((log     nil)
         (logger (lambda (&rest hooks) 
                   (setq log (append log hooks nil)))))
    (my/with-advice
        ((#'run-hooks :before logger))
      (call-interactively command))
    (when verbose
      (message
       (if log "Hooks run during execution of %s:"
         "No hooks run during execution of %s.")
       command)
      (dolist (hook log)
        (message "> %s" hook)))
    log))

To look at what hooks are run, we're using this macro for installing temporary advice. You could also do this with something like the noflet package, if you prefer.

(defmacro my/with-advice (adlist &rest body)
  "Execute BODY with temporary advice in ADLIST.

Each element of ADLIST should be a list of the form
  (SYMBOL WHERE FUNCTION [PROPS])
suitable for passing to `advice-add'.  The BODY is wrapped in an
`unwind-protect' form, so the advice will be removed even in the
event of an error or nonlocal exit."
  (declare (debug ((&rest (&rest form)) body))
           (indent 1))
  `(progn
     ,@(mapcar (lambda (adform)
                 (cons 'advice-add adform))
               adlist)
     (unwind-protect (progn ,@body)
       ,@(mapcar (lambda (adform)
                   `(advice-remove ,(car adform) ,(nth 2 adform)))
                 adlist))))

Note that run-hooks is a primitive, so in theory this is a little risky. In practice, I've found that advising most primitives with functions that don't try to change their underlying behavior (e.g., logging) doesn't seem to cause any problems.

A few caveats:

  1. This will only detect normal hooks, not abnormal hooks run with run-hook-with-args, run-hook-with-args-until-success, and so forth. You could of course modify it to see these too.

  2. A command might run a hook only in certain conditions. Naturally, you will only see these hooks if the conditions are met during this execution of the command.

  3. There's no way to know why a particular hook was executed. Maybe it's an intrinsic part of the command you're executing, or maybe it's being run by something that is itself in a different hook, or being run off a timer, etc.

  • 1
    This is great and will give me something to hack on! One disadvantage is that it actually has to run the command first. I found this out when I tried to see what hooks are run when you execute kill-emacs...) – elethan Jan 16 '16 at 18:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.