How can I use cl-letf
or similar to override a symbol's function value during an async call? I want to stop a buffer being displayed after calls to start-process
or start-process-shell-command
, and instead get back a string instead.
Here is a simplified example where binding display-buffer
works for the synchronous version but not the async version. Also, I have set lexical-binding to true.
(defun tst-fun-sync (url)
(call-process "wget" nil "*wget*" nil url "-O" "-")
(with-current-buffer "*wget*"
(display-buffer (current-buffer))))
(defun tst-fun-async (url)
(set-process-sentinel
(start-process "wget" "*wget*" "wget" url "-O" "-")
#'(lambda (p _m)
(when (zerop (process-exit-status p))
(with-current-buffer (process-buffer p)
(display-buffer (current-buffer)))))))
(defun tst-fun-no-display (fun &rest args)
(cl-letf (((symbol-function 'display-buffer)
#'(lambda (&rest _ignored)
(message "%s" (buffer-string)))))
(apply fun args)))
;; The first has desired result, but not the second
;; (tst-fun-no-display 'tst-fun-sync "http://www.stackoverflow.com")
;; (tst-fun-no-display 'tst-fun-async "http://www.stackoverflow.com")
Let's define a macro which temporarily rebinds set-process-sentinel
so that the sentinel function can be decorated with a wrapper function.
(defmacro with-sentinel-wrapper (wrapper-fn &rest body)
(let ((sps (gensym))
(proc (gensym))
(fn (gensym)))
`(let ((,sps (symbol-function 'set-process-sentinel)))
(cl-letf (((symbol-function 'set-process-sentinel)
(lambda (,proc ,fn)
(funcall ,sps ,proc (funcall ,wrapper-fn ,fn)))))
,@body))))
The wrapper can change the dynamic context in which the sentinel is called, by establishing any useful dynamic bindings. Here, I reuse your cl-letf
to change what display does:
(with-sentinel-wrapper (lambda (fn)
(lexical-let ((fun fn))
(lambda (p m)
(cl-letf (((symbol-function 'display-buffer)
#'(lambda (&rest _ignored)
(message "%s" (buffer-string)))))
(funcall fun p m)))))
(tst-fun-async "http://www.stackoverflow.com"))
Now, if you aren't sure that the asynchronous process actually calls set-process-sentinel
, you may have to hack other functions.