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