3

Is there a hook that would run as soon as control returns to the user (i.e. to the command loop)?

Context: I'd like to register a function to run as soon as all functions in the current sequence of recursive calls have exited. The call stack looks like ... -> a -> b -> ... -> my function, and I would like my function to run when all functions on the stack have exited.

More context: Somewhere up in the call chain to my function there is a function f that is ill-behaved, insofar as I can't call it from one of its children (it needs to do some cleanup before I can call it again). Since I can't call f from my function, I'd like to register the call to f to run as soon as f has completed.

1
  • 2
    The most common hook that runs after the current function is the post-command-hook -- there is at least one other hook that runs later in time, but not always -- the former always runs exactly one time. However, this is before control returns to the user. The hook that runs before the current function is called the pre-command-hook. Here is a link to the documentation regarding the standard hooks: gnu.org/software/emacs/manual/html_node/elisp/…
    – lawlist
    Apr 29, 2015 at 5:16

2 Answers 2

4

If you want to run something after the stack clears, run it on a 0 second timer.

(run-with-timer 0 nil 'your-function)

You could wrap this is a macro like this:

(defmacro after-stack-clears (&rest body)
  "Do BODY after the call stack is clear"
  `(run-with-timer 0 nil (lambda () ,@body)))

Here's an example usage mimicking your described scenario.

In this example, annoying-func is calling another provided function. In the provided function you'd like to call annoying-func again but annoying-func will error if called more than once on the call stack. So to counter this, the provided macro to ensure that annoying-func is called a second time, but only after the call stack has cleared and annoying-func has done its cleanup

(defun your-func-simple ()
  "try to directly call `annoying-func'."
  (annoying-func (lambda () (message "hi"))))

(defun your-func-smart ()
  "ensure `annoying-func' is called after the stack clears."
  (after-stack-clears
   (annoying-func (lambda () (message "hi")))))

(setq annoying-var nil)

(defun annoying-func (func)
  "This function will apply FUNC, if `annoying-function' is called
somewhere down the line by FUNC, the second call will error, because this 
is the annoying function after all."
  (if annoying-var
      (error "Can't call recursively!")
    (condition-case err
        (progn (setq annoying-var t)
               (apply func nil)
               (setq annoying-var nil))
      (error (progn (message "errored! %s" err) (setq annoying-var nil))))))


(defun erroring-example ()
  (annoying-func 'your-func-simple))

(defun working-example ()
  (annoying-func 'your-func-smart))

;; will error due to annoying-func being called twice on the stack
(erroring-example) 

;; will NOT error since annoying-func is called again only after the stack has cleared
(working-example) 
4
  • Just keep in mind there can be subtle issues with keymaps and timers: lists.gnu.org/archive/html/emacs-pretest-bug/2007-04/… May 1, 2015 at 2:55
  • There are definitely additional concerns one must note with timers, but I'm not sure that bug is still relevant, it is from 2007 and I can't replicate it on Emacs 25. May 1, 2015 at 13:11
  • I still see the bug in 24.3. By the way, I had also up-voted your answer. May 1, 2015 at 14:34
  • Ah, well 24.3 is still used by many so good to know, thanks for pointing that out. May 1, 2015 at 14:56
2

As lawlist says, you can use post-command-hook for that.

But another option might be to add an advice to the offending function. Along the lines of:

(advice-add 'f :after
            (lambda (&rest args)
              <do-something-after-f-runs>))
2
  • Does advising work if the function is already executing?
    – Clément
    Apr 29, 2015 at 11:50
  • No, you want to advise once and for all from your ~/.emacs, or from the top-level of your package.
    – Stefan
    Apr 29, 2015 at 13:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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