1 ;;; async.el --- Asynchronous processing in Emacs
3 ;; Copyright (C) 2012-2016 Free Software Foundation, Inc.
5 ;; Author: John Wiegley <jwiegley@gmail.com>
6 ;; Created: 18 Jun 2012
10 ;; X-URL: https://github.com/jwiegley/emacs-async
12 ;; This program is free software; you can redistribute it and/or
13 ;; modify it under the terms of the GNU General Public License as
14 ;; published by the Free Software Foundation; either version 2, or (at
15 ;; your option) any later version.
17 ;; This program is distributed in the hope that it will be useful, but
18 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 ;; General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
29 ;; Adds the ability to call asynchronous functions and process with ease. See
30 ;; the documentation for `async-start' and `async-start-process'.
35 "Simple asynchronous processing in Emacs"
38 (defvar async-debug nil)
39 (defvar async-send-over-pipe t)
40 (defvar async-in-child-emacs nil)
41 (defvar async-callback nil)
42 (defvar async-callback-for-process nil)
43 (defvar async-callback-value nil)
44 (defvar async-callback-value-set nil)
45 (defvar async-current-process nil)
46 (defvar async--procvar nil)
48 (defun async-inject-variables
49 (include-regexp &optional predicate exclude-regexp)
50 "Return a `setq' form that replicates part of the calling environment.
51 It sets the value for every variable matching INCLUDE-REGEXP and
52 also PREDICATE. It will not perform injection for any variable
53 matching EXCLUDE-REGEXP (if present). It is intended to be used
60 (insert ,(buffer-substring-no-properties (point-min) (point-max)))
61 ;; Pass in the variable environment for smtpmail
62 ,(async-inject-variables \"\\`\\(smtpmail\\|\\(user-\\)?mail\\)-\")
70 (or (null include-regexp)
71 (string-match include-regexp (symbol-name sym)))
73 (or exclude-regexp "-syntax-table\\'")
75 (let ((value (symbol-value sym)))
76 (when (or (null predicate)
77 (funcall predicate sym))
78 (setq bindings (cons `(quote ,value) bindings)
79 bindings (cons sym bindings)))))))
82 (defalias 'async-inject-environment 'async-inject-variables)
84 (defun async-handle-result (func result buf)
87 (set (make-local-variable 'async-callback-value) result)
88 (set (make-local-variable 'async-callback-value-set) t))
90 (if (and (listp result)
91 (eq 'async-signal (nth 0 result)))
92 (signal (car (nth 1 result))
94 (funcall func result))
98 (defun async-when-done (proc &optional change)
99 "Process sentinal used to retrieve the value from the child process."
100 (when (eq 'exit (process-status proc))
101 (with-current-buffer (process-buffer proc)
102 (let ((async-current-process proc))
103 (if (= 0 (process-exit-status proc))
104 (if async-callback-for-process
107 (funcall async-callback proc)
109 (kill-buffer (current-buffer))))
110 (set (make-local-variable 'async-callback-value) proc)
111 (set (make-local-variable 'async-callback-value-set) t))
112 (goto-char (point-max))
114 (async-handle-result async-callback (read (current-buffer))
116 (set (make-local-variable 'async-callback-value)
118 (format "Async process '%s' failed with exit code %d"
119 (process-name proc) (process-exit-status proc))))
120 (set (make-local-variable 'async-callback-value-set) t))))))
122 (defun async--receive-sexp (&optional stream)
123 (let ((sexp (decode-coding-string (base64-decode-string
124 (read stream)) 'utf-8-unix))
125 ;; Parent expects UTF-8 encoded text.
126 (coding-system-for-write 'utf-8-unix))
128 (message "Received sexp {{{%s}}}" (pp-to-string sexp)))
129 (setq sexp (read sexp))
131 (message "Read sexp {{{%s}}}" (pp-to-string sexp)))
134 (defun async--insert-sexp (sexp)
137 (print-escape-nonascii t)
139 (prin1 sexp (current-buffer))
140 ;; Just in case the string we're sending might contain EOF
141 (encode-coding-region (point-min) (point-max) 'utf-8-unix)
142 (base64-encode-region (point-min) (point-max) t)
143 (goto-char (point-min)) (insert ?\")
144 (goto-char (point-max)) (insert ?\" ?\n)))
146 (defun async--transmit-sexp (process sexp)
149 (message "Transmitting sexp {{{%s}}}" (pp-to-string sexp)))
150 (async--insert-sexp sexp)
151 (process-send-region process (point-min) (point-max))))
153 (defun async-batch-invoke ()
154 "Called from the child Emacs process' command-line."
155 ;; Make sure 'message' and 'prin1' encode stuff in UTF-8, as parent
157 (let ((coding-system-for-write 'utf-8-unix))
158 (setq async-in-child-emacs t
159 debug-on-error async-debug)
162 (async--receive-sexp (unless async-send-over-pipe
163 command-line-args-left))))
166 (async--receive-sexp (unless async-send-over-pipe
167 command-line-args-left))))
169 (prin1 (list 'async-signal err)))))))
171 (defun async-ready (future)
172 "Query a FUTURE to see if the ready is ready -- i.e., if no blocking
173 would result from a call to `async-get' on that FUTURE."
174 (and (memq (process-status future) '(exit signal))
175 (with-current-buffer (process-buffer future)
176 async-callback-value-set)))
178 (defun async-wait (future)
179 "Wait for FUTURE to become ready."
180 (while (not (async-ready future))
183 (defun async-get (future)
184 "Get the value from an asynchronously function when it is ready.
185 FUTURE is returned by `async-start' or `async-start-process' when
186 its FINISH-FUNC is nil."
188 (with-current-buffer (process-buffer future)
189 (async-handle-result #'identity async-callback-value (current-buffer))))
191 (defun async-message-p (value)
192 "Return true of VALUE is an async.el message packet."
194 (plist-get value :async-message)))
196 (defun async-send (&rest args)
197 "Send the given messages to the asychronous Emacs PROCESS."
198 (let ((args (append args '(:async-message t))))
199 (if async-in-child-emacs
201 (funcall async-callback args))
202 (async--transmit-sexp (car args) (list 'quote (cdr args))))))
204 (defun async-receive (&rest args)
205 "Send the given messages to the asychronous Emacs PROCESS."
206 (async--receive-sexp))
209 (defun async-start-process (name program finish-func &rest program-args)
210 "Start the executable PROGRAM asynchronously. See `async-start'.
211 PROGRAM is passed PROGRAM-ARGS, calling FINISH-FUNC with the
212 process object when done. If FINISH-FUNC is nil, the future
213 object will return the process object when the program is
214 finished. Set DEFAULT-DIRECTORY to change PROGRAM's current
216 (let* ((buf (generate-new-buffer (concat "*" name "*")))
217 (proc (let ((process-connection-type nil))
218 (apply #'start-process name buf program program-args))))
219 (with-current-buffer buf
220 (set (make-local-variable 'async-callback) finish-func)
221 (set-process-sentinel proc #'async-when-done)
222 (unless (string= name "emacs")
223 (set (make-local-variable 'async-callback-for-process) t))
227 (defun async-start (start-func &optional finish-func)
228 "Execute START-FUNC (often a lambda) in a subordinate Emacs process.
229 When done, the return value is passed to FINISH-FUNC. Example:
232 ;; What to do in the child process
234 (message \"This is a test\")
238 ;; What to do when it finishes
240 (message \"Async process done, result should be 222: %s\"
243 If FINISH-FUNC is nil or missing, a future is returned that can
244 be inspected using `async-get', blocking until the value is
247 (let ((proc (async-start
248 ;; What to do in the child process
250 (message \"This is a test\")
254 (message \"I'm going to do some work here\") ;; ....
256 (message \"Waiting on async process, result should be 222: %s\"
259 If you don't want to use a callback, and you don't care about any
260 return value form the child process, pass the `ignore' symbol as
261 the second argument (if you don't, and never call `async-get', it
262 will leave *emacs* process buffers hanging around):
266 (delete-file \"a remote file on a slow link\" nil))
269 Note: Even when FINISH-FUNC is present, a future is still
270 returned except that it yields no value (since the value is
271 passed to FINISH-FUNC). Call `async-get' on such a future always
272 returns nil. It can still be useful, however, as an argument to
273 `async-ready' or `async-wait'."
274 (let ((sexp start-func)
275 ;; Subordinate Emacs will send text encoded in UTF-8.
276 (coding-system-for-read 'utf-8-unix))
279 "emacs" (file-truename
280 (expand-file-name invocation-name
281 invocation-directory))
284 ;; Using `locate-library' ensure we use the right file
285 ;; when the .elc have been deleted.
286 (locate-library "async")
287 "-batch" "-f" "async-batch-invoke"
288 (if async-send-over-pipe
291 (async--insert-sexp (list 'quote sexp))
293 (if async-send-over-pipe
294 (async--transmit-sexp async--procvar (list 'quote sexp)))
297 (defmacro async-sandbox(func)
298 "Evaluate FUNC in a separate Emacs process, synchronously."
299 `(async-get (async-start ,func)))
303 ;;; async.el ends here