]> code.delx.au - gnu-emacs-elpa/blob - packages/async/async.el
Update version of async.el to 1.6
[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 (if async-debug
126 (message "Received sexp {{{%s}}}" (pp-to-string sexp)))
127 (setq sexp (read sexp))
128 (if async-debug
129 (message "Read sexp {{{%s}}}" (pp-to-string sexp)))
130 (eval sexp)))
131
132 (defun async--insert-sexp (sexp)
133 (let (print-level print-length)
134 (prin1 sexp (current-buffer))
135 ;; Just in case the string we're sending might contain EOF
136 (encode-coding-region (point-min) (point-max) 'utf-8-unix)
137 (base64-encode-region (point-min) (point-max) t)
138 (goto-char (point-min)) (insert ?\")
139 (goto-char (point-max)) (insert ?\" ?\n)))
140
141 (defun async--transmit-sexp (process sexp)
142 (with-temp-buffer
143 (if async-debug
144 (message "Transmitting sexp {{{%s}}}" (pp-to-string sexp)))
145 (async--insert-sexp sexp)
146 (process-send-region process (point-min) (point-max))))
147
148 (defun async-batch-invoke ()
149 "Called from the child Emacs process' command-line."
150 (setq async-in-child-emacs t
151 debug-on-error async-debug)
152 (if debug-on-error
153 (prin1 (funcall
154 (async--receive-sexp (unless async-send-over-pipe
155 command-line-args-left))))
156 (condition-case err
157 (prin1 (funcall
158 (async--receive-sexp (unless async-send-over-pipe
159 command-line-args-left))))
160 (error
161 (prin1 (list 'async-signal err))))))
162
163 (defun async-ready (future)
164 "Query a FUTURE to see if the ready is ready -- i.e., if no blocking
165 would result from a call to `async-get' on that FUTURE."
166 (and (memq (process-status future) '(exit signal))
167 (with-current-buffer (process-buffer future)
168 async-callback-value-set)))
169
170 (defun async-wait (future)
171 "Wait for FUTURE to become ready."
172 (while (not (async-ready future))
173 (sit-for 0.05)))
174
175 (defun async-get (future)
176 "Get the value from an asynchronously function when it is ready.
177 FUTURE is returned by `async-start' or `async-start-process' when
178 its FINISH-FUNC is nil."
179 (async-wait future)
180 (with-current-buffer (process-buffer future)
181 (async-handle-result #'identity async-callback-value (current-buffer))))
182
183 (defun async-message-p (value)
184 "Return true of VALUE is an async.el message packet."
185 (and (listp value)
186 (plist-get value :async-message)))
187
188 (defun async-send (&rest args)
189 "Send the given messages to the asychronous Emacs PROCESS."
190 (let ((args (append args '(:async-message t))))
191 (if async-in-child-emacs
192 (if async-callback
193 (funcall async-callback args))
194 (async--transmit-sexp (car args) (list 'quote (cdr args))))))
195
196 (defun async-receive (&rest args)
197 "Send the given messages to the asychronous Emacs PROCESS."
198 (async--receive-sexp))
199
200 ;;;###autoload
201 (defun async-start-process (name program finish-func &rest program-args)
202 "Start the executable PROGRAM asynchronously. See `async-start'.
203 PROGRAM is passed PROGRAM-ARGS, calling FINISH-FUNC with the
204 process object when done. If FINISH-FUNC is nil, the future
205 object will return the process object when the program is
206 finished. Set DEFAULT-DIRECTORY to change PROGRAM's current
207 working directory."
208 (let* ((buf (generate-new-buffer (concat "*" name "*")))
209 (proc (let ((process-connection-type nil))
210 (apply #'start-process name buf program program-args))))
211 (with-current-buffer buf
212 (set (make-local-variable 'async-callback) finish-func)
213 (set-process-sentinel proc #'async-when-done)
214 (unless (string= name "emacs")
215 (set (make-local-variable 'async-callback-for-process) t))
216 proc)))
217
218 ;;;###autoload
219 (defun async-start (start-func &optional finish-func)
220 "Execute START-FUNC (often a lambda) in a subordinate Emacs process.
221 When done, the return value is passed to FINISH-FUNC. Example:
222
223 (async-start
224 ;; What to do in the child process
225 (lambda ()
226 (message \"This is a test\")
227 (sleep-for 3)
228 222)
229
230 ;; What to do when it finishes
231 (lambda (result)
232 (message \"Async process done, result should be 222: %s\"
233 result)))
234
235 If FINISH-FUNC is nil or missing, a future is returned that can
236 be inspected using `async-get', blocking until the value is
237 ready. Example:
238
239 (let ((proc (async-start
240 ;; What to do in the child process
241 (lambda ()
242 (message \"This is a test\")
243 (sleep-for 3)
244 222))))
245
246 (message \"I'm going to do some work here\") ;; ....
247
248 (message \"Waiting on async process, result should be 222: %s\"
249 (async-get proc)))
250
251 If you don't want to use a callback, and you don't care about any
252 return value form the child process, pass the `ignore' symbol as
253 the second argument (if you don't, and never call `async-get', it
254 will leave *emacs* process buffers hanging around):
255
256 (async-start
257 (lambda ()
258 (delete-file \"a remote file on a slow link\" nil))
259 'ignore)
260
261 Note: Even when FINISH-FUNC is present, a future is still
262 returned except that it yields no value (since the value is
263 passed to FINISH-FUNC). Call `async-get' on such a future always
264 returns nil. It can still be useful, however, as an argument to
265 `async-ready' or `async-wait'."
266 (let ((sexp start-func))
267 (setq async--procvar
268 (async-start-process
269 "emacs" (file-truename
270 (expand-file-name invocation-name
271 invocation-directory))
272 finish-func
273 "-Q" "-l"
274 ;; Using `locate-library' ensure we use the right file
275 ;; when the .elc have been deleted.
276 (locate-library "async")
277 "-batch" "-f" "async-batch-invoke"
278 (if async-send-over-pipe
279 "<none>"
280 (with-temp-buffer
281 (async--insert-sexp (list 'quote sexp))
282 (buffer-string)))))
283 (if async-send-over-pipe
284 (async--transmit-sexp async--procvar (list 'quote sexp)))
285 async--procvar))
286
287 (defmacro async-sandbox(func)
288 "Evaluate FUNC in a separate Emacs process, synchronously."
289 `(async-get (async-start ,func)))
290
291 (provide 'async)
292
293 ;;; async.el ends here