]> code.delx.au - gnu-emacs-elpa/blob - packages/async/async.el
Merge commit 'b67364d4825a9bf0a22261809ee9e9060b268198'
[gnu-emacs-elpa] / packages / async / async.el
1 ;;; async.el --- Asynchronous processing in Emacs
2
3 ;; Copyright (C) 2012-2016 Free Software Foundation, Inc.
4
5 ;; Author: John Wiegley <jwiegley@gmail.com>
6 ;; Created: 18 Jun 2012
7 ;; Version: 1.6
8
9 ;; Keywords: async
10 ;; X-URL: https://github.com/jwiegley/emacs-async
11
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.
16
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.
21
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.
26 \f
27 ;;; Commentary:
28
29 ;; Adds the ability to call asynchronous functions and process with ease. See
30 ;; the documentation for `async-start' and `async-start-process'.
31 \f
32 ;;; Code:
33
34 (defgroup async nil
35 "Simple asynchronous processing in Emacs"
36 :group 'emacs)
37
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)
47
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
54 as follows:
55
56 (async-start
57 `(lambda ()
58 (require 'smtpmail)
59 (with-temp-buffer
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\\)-\")
63 (smtpmail-send-it)))
64 'ignore)"
65 `(setq
66 ,@(let (bindings)
67 (mapatoms
68 (lambda (sym)
69 (if (and (boundp sym)
70 (or (null include-regexp)
71 (string-match include-regexp (symbol-name sym)))
72 (not (string-match
73 (or exclude-regexp "-syntax-table\\'")
74 (symbol-name sym))))
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)))))))
80 bindings)))
81
82 (defalias 'async-inject-environment 'async-inject-variables)
83
84 (defun async-handle-result (func result buf)
85 (if (null func)
86 (progn
87 (set (make-local-variable 'async-callback-value) result)
88 (set (make-local-variable 'async-callback-value-set) t))
89 (unwind-protect
90 (if (and (listp result)
91 (eq 'async-signal (nth 0 result)))
92 (signal (car (nth 1 result))
93 (cdr (nth 1 result)))
94 (funcall func result))
95 (unless async-debug
96 (kill-buffer buf)))))
97
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
105 (if async-callback
106 (prog1
107 (funcall async-callback proc)
108 (unless async-debug
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))
113 (backward-sexp)
114 (async-handle-result async-callback (read (current-buffer))
115 (current-buffer)))
116 (set (make-local-variable 'async-callback-value)
117 (list 'error
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))))))
121
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))
127 (if async-debug
128 (message "Received sexp {{{%s}}}" (pp-to-string sexp)))
129 (setq sexp (read sexp))
130 (if async-debug
131 (message "Read sexp {{{%s}}}" (pp-to-string sexp)))
132 (eval sexp)))
133
134 (defun async--insert-sexp (sexp)
135 (let (print-level
136 print-length
137 (print-escape-nonascii t)
138 (print-circle 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)))
145
146 (defun async--transmit-sexp (process sexp)
147 (with-temp-buffer
148 (if async-debug
149 (message "Transmitting sexp {{{%s}}}" (pp-to-string sexp)))
150 (async--insert-sexp sexp)
151 (process-send-region process (point-min) (point-max))))
152
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
156 ;; process expects.
157 (let ((coding-system-for-write 'utf-8-unix))
158 (setq async-in-child-emacs t
159 debug-on-error async-debug)
160 (if debug-on-error
161 (prin1 (funcall
162 (async--receive-sexp (unless async-send-over-pipe
163 command-line-args-left))))
164 (condition-case err
165 (prin1 (funcall
166 (async--receive-sexp (unless async-send-over-pipe
167 command-line-args-left))))
168 (error
169 (prin1 (list 'async-signal err)))))))
170
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)))
177
178 (defun async-wait (future)
179 "Wait for FUTURE to become ready."
180 (while (not (async-ready future))
181 (sit-for 0.05)))
182
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."
187 (async-wait future)
188 (with-current-buffer (process-buffer future)
189 (async-handle-result #'identity async-callback-value (current-buffer))))
190
191 (defun async-message-p (value)
192 "Return true of VALUE is an async.el message packet."
193 (and (listp value)
194 (plist-get value :async-message)))
195
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
200 (if async-callback
201 (funcall async-callback args))
202 (async--transmit-sexp (car args) (list 'quote (cdr args))))))
203
204 (defun async-receive (&rest args)
205 "Send the given messages to the asychronous Emacs PROCESS."
206 (async--receive-sexp))
207
208 ;;;###autoload
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
215 working directory."
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))
224 proc)))
225
226 ;;;###autoload
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:
230
231 (async-start
232 ;; What to do in the child process
233 (lambda ()
234 (message \"This is a test\")
235 (sleep-for 3)
236 222)
237
238 ;; What to do when it finishes
239 (lambda (result)
240 (message \"Async process done, result should be 222: %s\"
241 result)))
242
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
245 ready. Example:
246
247 (let ((proc (async-start
248 ;; What to do in the child process
249 (lambda ()
250 (message \"This is a test\")
251 (sleep-for 3)
252 222))))
253
254 (message \"I'm going to do some work here\") ;; ....
255
256 (message \"Waiting on async process, result should be 222: %s\"
257 (async-get proc)))
258
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):
263
264 (async-start
265 (lambda ()
266 (delete-file \"a remote file on a slow link\" nil))
267 'ignore)
268
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))
277 (setq async--procvar
278 (async-start-process
279 "emacs" (file-truename
280 (expand-file-name invocation-name
281 invocation-directory))
282 finish-func
283 "-Q" "-l"
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
289 "<none>"
290 (with-temp-buffer
291 (async--insert-sexp (list 'quote sexp))
292 (buffer-string)))))
293 (if async-send-over-pipe
294 (async--transmit-sexp async--procvar (list 'quote sexp)))
295 async--procvar))
296
297 (defmacro async-sandbox(func)
298 "Evaluate FUNC in a separate Emacs process, synchronously."
299 `(async-get (async-start ,func)))
300
301 (provide 'async)
302
303 ;;; async.el ends here