4

From the Elisp manual at the Sentinels chapter:

The sentinel receives two arguments: the process for which the event occurred, and a string describing the type of event.

But I need it to receive one more argument, because at the end of the process, the context (like current buffer) may have changed.

(defun faustine-mdoc (&optional build-all)
  "Generate mdoc of the current file, display it in a buffer."
  (interactive)
  (let ((files-to-build (if build-all
                            (mapconcat 'identity (project-files (buffer-name) '()) " ")
                          (current-buffer))))
    (set-process-sentinel
     (start-process-shell-command
      "Mdoc"
      output-buffer-name (format "command %s" files-to-build)) 'mdoc-sentinel)))

(defun mdoc-sentinel (process event)
  "mdoc sentinel"
  (let ((pdf-file (format "%s-mdoc/pdf/%s.pdf"
                          (file-name-sans-extension
                           (buffer-name))  ; <- See? This is not the right buffer
                          (file-name-sans-extension
                           (buffer-name)))))
    (log-to-buffer process event)
    (when (string-prefix-p "finished" event)
      (faustine-show pdf-file))))

How can I pass arguments to a sentinel? More generally, how to pass arguments in a quoted function call?

  • The classic workaround is to store/retrieve process-specific data. – wasamasa Jul 28 '17 at 21:08
  • This question needs an 'elisp tag, IMO. – Peder Klingenberg Jul 28 '17 at 21:32
  • IMO the question should not have an elisp tag. It is not a question about the nature of the Elisp language; it is a question about how to use Emacs-Lisp thingies (sentinels) to do something. The same would be true for a question about how to append two lists or how to bind a key.This question is about using Elisp to accomplish something, not about its design or nature. – Drew Jul 29 '17 at 0:36
5

The classic method is to use buffer-local variables in the buffer associated with the process.

(defvar faustine-process-source-buffer nil
  "Source buffer from which the current process is generating mdoc.")
(make-variable-buffer-local 'faustine-process-source-buffer)
(defun faustine-mdoc (&optional build-all)
  "Generate mdoc of the current file, display it in a buffer."
  (interactive)
  (let* ((files-to-build (if build-all
                             (mapconcat 'identity (project-files (buffer-name) '()) " ")
                           (current-buffer)))
         (process (start-process-shell-command "Mdoc"
                                               output-buffer-name
                                               (format "command %s" files-to-build))))
    (with-current-buffer (process-buffer process)
      (setq faustine-process-source-buffer (current-buffer)))
    (set-process-sentinel process 'mdoc-sentinel)))

(defun mdoc-sentinel (process event)
  "mdoc sentinel"
  (let ((pdf-file (format "%s-mdoc/pdf/%s.pdf"
                          (file-name-sans-extension
                           (buffer-name faustine-process-source-buffer))
                          (file-name-sans-extension
                           (buffer-name faustine-process-source-buffer)))))
    (log-to-buffer process event)
    (when (string-prefix-p "finished" event)
      (faustine-show pdf-file))))

Alternatively, you can use lexical scoping to make a closure embedding the values of variables in the function object.

(require 'cl)
(defun faustine-mdoc (&optional build-all)
  "Generate mdoc of the current file, display it in a buffer."
  (interactive)
  (let ((files-to-build (if build-all
                            (mapconcat 'identity (project-files (buffer-name) '()) " ")
                          (current-buffer))))
    (set-process-sentinel
     (start-process-shell-command
      "Mdoc"
      output-buffer-name (format "command %s" files-to-build))
     (mdoc-make-sentinel (current-buffer)))))

(defun mdoc-make-sentinel (source-buffer)
  (lexical-let ((source-buffer source-buffer))
    (lambda (process event)
      "mdoc sentinel"
      (let ((pdf-file (format "%s-mdoc/pdf/%s.pdf"
                              (file-name-sans-extension
                               (buffer-name source-buffer))
                              (file-name-sans-extension
                               (buffer-name source-buffer)))))
        (log-to-buffer process event)
        (when (string-prefix-p "finished" event)
          (faustine-show pdf-file))))))
  • Wow, nice. But the lexical-let macro (gnu.org/software/emacs/manual/html_node/cl/…) doesn't seem to be available in my Emacs 24.5.1 : mdoc-make-sentinel: Symbol's function definition is void: lexical-let? The linked GNU doc is confusing : Are we to understand that plain old "let" is now lexical scoping-compatible? Or is this "let*"? Thanks! – yPhil Jul 29 '17 at 8:29
  • Note that your 1st proposed solution worked right out of the box :) Still, I would have loved that lexical thing to work :| – yPhil Jul 29 '17 at 8:57
  • 1
    @yPhil My mistake, lexical-let is from the cl package. This should work even in earlier Emacs version (but note I haven't tested the code at all). – Gilles Jul 29 '17 at 12:44
  • 3
    I would not use lexical-let at all and let lexical-binding do its magic. – wasamasa Jul 29 '17 at 14:44
3

I'm not sure what you mean by "pass arguments in a quoted function call", but if you're referring to the 'mdoc-sentinel argument to set-process-sentinel, that is not a function call, you are just passing a symbol, more or less the name of the function. In general, how that function is called is not up to the caller. And as the documentation states, the function you specify will receive exactly two arguments. You can't change this.

A trick used by e.g. the built in URL package is to allow callers to specify a callback function and arguments to that function (see the signature of url-retrieve). That callback function is not used directly as a sentinel. Instead, both callback function and its arguments are stored in buffer-local variables in the buffer associated with the async process, and a static sentinel function does basically (apply callback callback-args).

So essentially a you need a wrapper around the basic sentinel functionality. It's a bit fiddly, you need to make sure you know which buffer the process is associated with, make/set some buffer local variables in that buffer, and have the sentinel function call your callback with the correct arguments. But as the URL library shows, it can be effectively hidden in a library.

This is how EWW, a client of the URL library, keeps track of which buffer to render stuff in across async calls, which looks similar to what you're asking.

EWW uses eww-render as its callback function, and when calling url-retrieve, includes (current-buffer) in its list of callback arguments, which allows eww-render to switch to the correct buffer for rendering.

I recommend studying eww.el, url.el and url-http.el from your nearest emacs sources. There's quite a bit of detail I've omitted in the hope of getting the idea across.

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.