]> code.delx.au - gnu-emacs-elpa/blob - async.el
Fix for when async.el is byte-compiled
[gnu-emacs-elpa] / async.el
1 ;;; async --- Asynchronous processing in Emacs
2
3 ;; Copyright (C) 2012 John Wiegley
4
5 ;; Author: John Wiegley <jwiegley@gmail.com>
6 ;; Created: 18 Jun 2012
7 ;; Version: 1.1
8 ;; Keywords: async
9 ;; X-URL: https://github.com/jwiegley/emacs-async
10
11 ;; This program is free software; you can redistribute it and/or
12 ;; modify it under the terms of the GNU General Public License as
13 ;; published by the Free Software Foundation; either version 2, or (at
14 ;; your option) any later version.
15
16 ;; This program is distributed in the hope that it will be useful, but
17 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 ;; General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
25 \f
26 ;;; Commentary:
27
28 ;; Adds the ability to call asynchronous functions and process with ease. See
29 ;; the documentation for `async-start' and `async-start-process'.
30 \f
31 ;;; Code:
32
33 (defgroup async nil
34 "Simple asynchronous processing in Emacs"
35 :group 'emacs)
36
37 (defvar async-debug nil)
38 (defvar async-send-over-pipe nil)
39 (defvar async-in-child-emacs nil)
40 (defvar async-callback nil)
41 (defvar async-callback-for-process nil)
42 (defvar async-callback-value nil)
43 (defvar async-callback-value-set nil)
44 (defvar async-current-process nil)
45
46 (defun async-inject-variables
47 (include-regexp &optional predicate exclude-regexp)
48 "Return a `setq' form that replicates part of the calling environment.
49 It sets the value for every variable matching INCLUDE-REGEXP and
50 also PREDICATE. It will not perform injection for any variable
51 matching EXCLUDE-REGEXP (if present). It is intended to be used
52 as follows:
53
54 (async-start
55 `(lambda ()
56 (require 'smtpmail)
57 (with-temp-buffer
58 (insert ,(buffer-substring-no-properties (point-min) (point-max)))
59 ;; Pass in the variable environment for smtpmail
60 ,(async-inject-variables \"\\`\\(smtpmail\\|\\(user-\\)?mail\\)-\")
61 (smtpmail-send-it)))
62 'ignore)"
63 `(setq
64 ,@(let (bindings)
65 (mapatoms
66 (lambda (sym)
67 (if (and (boundp sym)
68 (or (null include-regexp)
69 (string-match include-regexp (symbol-name sym)))
70 (not (string-match
71 (or exclude-regexp "-syntax-table\\'")
72 (symbol-name sym))))
73 (let ((value (symbol-value sym)))
74 (when (or (null predicate)
75 (funcall predicate sym))
76 (setq bindings (cons `(quote ,value) bindings)
77 bindings (cons sym bindings)))))))
78 bindings)))
79
80 (defalias 'async-inject-environment 'async-inject-variables)
81
82 (defun async-when-done (proc &optional change)
83 "Process sentinal used to retrieve the value from the child process."
84 (when (eq 'exit (process-status proc))
85 (with-current-buffer (process-buffer proc)
86 (let ((async-current-process proc))
87 (if (= 0 (process-exit-status proc))
88 (if async-callback-for-process
89 (if async-callback
90 (prog1
91 (funcall async-callback proc)
92 (unless async-debug
93 (kill-buffer (current-buffer))))
94 (set (make-local-variable 'async-callback-value) proc)
95 (set (make-local-variable 'async-callback-value-set) t))
96 (goto-char (point-max))
97 (backward-sexp)
98 (let ((result (read (current-buffer))))
99 (if (and (listp result)
100 (eq 'async-signal (car result)))
101 (if (eq 'error (car (cdr result)))
102 (error (cadr (cdr result)))
103 (signal (cadr result)
104 (cddr result)))
105 (if async-callback
106 (prog1
107 (funcall async-callback result)
108 (unless async-debug
109 (kill-buffer (current-buffer))))
110 (set (make-local-variable 'async-callback-value) result)
111 (set (make-local-variable 'async-callback-value-set) t)))))
112 (set (make-local-variable 'async-callback-value) 'error)
113 (set (make-local-variable 'async-callback-value-set) t)
114 (error "Async process '%s' failed with exit code %d"
115 (process-name proc) (process-exit-status proc)))))))
116
117 (defun async--receive-sexp (&optional stream)
118 (let ((sexp (if async-send-over-pipe
119 (read (base64-decode-string (read stream)))
120 (read stream))))
121 (if async-debug
122 (message "Received sexp {{{%s}}}" (pp-to-string sexp)))
123 (eval sexp)))
124
125 (defun async--insert-sexp (sexp)
126 (if async-send-over-pipe
127 (progn
128 (prin1 sexp (current-buffer))
129 ;; Just in case the string we're sending might contain EOF
130 (base64-encode-region (point-min) (point-max) t)
131 (goto-char (point-min)) (insert ?\")
132 (goto-char (point-max)) (insert ?\" ?\n))
133 (let ((print-escape-newlines t))
134 (prin1 sexp (current-buffer)))
135 (insert ?\n)))
136
137 (defun async--transmit-sexp (process sexp)
138 (with-temp-buffer
139 (if async-debug
140 (message "Transmitting sexp {{{%s}}}" (pp-to-string sexp)))
141 (async--insert-sexp sexp)
142 (process-send-region process (point-min) (point-max))))
143
144 (defun async-batch-invoke ()
145 "Called from the child Emacs process' command-line."
146 (setq async-in-child-emacs t)
147 (condition-case err
148 (prin1 (funcall
149 (async--receive-sexp (unless async-send-over-pipe
150 command-line-args-left))))
151 (error
152 (backtrace)
153 (prin1 `(async-signal . ,err)))))
154
155 (defun async-ready (future)
156 "Query a FUTURE to see if the ready is ready -- i.e., if no blocking
157 would result from a call to `async-get' on that FUTURE."
158 (and (eq 'exit (process-status future))
159 async-callback-value-set))
160
161 (defun async-wait (future)
162 "Wait for FUTURE to become ready."
163 (while (not (async-ready future))
164 (sit-for 0.05)))
165
166 (defun async-get (future)
167 "Get the value from an asynchronously function when it is ready.
168 FUTURE is returned by `async-start' or `async-start-process' when
169 its FINISH-FUNC is nil."
170 (async-wait future)
171 (with-current-buffer (process-buffer future)
172 (prog1
173 async-callback-value
174 (kill-buffer (current-buffer)))))
175
176 (defun async-message-p (value)
177 "Return true of VALUE is an async.el message packet."
178 (and (listp value)
179 (plist-get value :async-message)))
180
181 (defun async-send (&rest args)
182 "Send the given messages to the asychronous Emacs PROCESS."
183 (let ((args (append args '(:async-message t))))
184 (if async-in-child-emacs
185 (if async-callback
186 (funcall async-callback args))
187 (async--transmit-sexp (car args) (list 'quote (cdr args))))))
188
189 (defun async-receive (&rest args)
190 "Send the given messages to the asychronous Emacs PROCESS."
191 (async--receive-sexp))
192
193 ;;;###autoload
194 (defun async-start-process (name program finish-func &rest program-args)
195 "Start the executable PROGRAM asynchronously. See `async-start'.
196 PROGRAM is passed PROGRAM-ARGS, calling FINISH-FUNC with the
197 process object when done. If FINISH-FUNC is nil, the future
198 object will return the process object when the program is
199 finished."
200 (let* ((buf (generate-new-buffer (concat "*" name "*")))
201 (proc (apply #'start-process name buf program program-args)))
202 (with-current-buffer buf
203 (set (make-local-variable 'async-callback) finish-func)
204 (set-process-sentinel proc #'async-when-done)
205 (unless (string= name "emacs")
206 (set (make-local-variable 'async-callback-for-process) t))
207 proc)))
208
209 ;;;###autoload
210 (defmacro async-start (start-func &optional finish-func)
211 "Execute START-FUNC (often a lambda) in a subordinate Emacs process.
212 When done, the return value is passed to FINISH-FUNC. Example:
213
214 (async-start
215 ;; What to do in the child process
216 (lambda ()
217 (message \"This is a test\")
218 (sleep-for 3)
219 222)
220
221 ;; What to do when it finishes
222 (lambda (result)
223 (message \"Async process done, result should be 222: %s\"
224 result)))
225
226 If FINISH-FUNC is nil or missing, a future is returned that can
227 be inspected using `async-get', blocking until the value is
228 ready. Example:
229
230 (let ((proc (async-start
231 ;; What to do in the child process
232 (lambda ()
233 (message \"This is a test\")
234 (sleep-for 3)
235 222))))
236
237 (message \"I'm going to do some work here\") ;; ....
238
239 (message \"Waiting on async process, result should be 222: %s\"
240 (async-get proc)))
241
242 If you don't want to use a callback, and you don't care about any
243 return value form the child process, pass the `ignore' symbol as
244 the second argument (if you don't, and never call `async-get', it
245 will leave *emacs* process buffers hanging around):
246
247 (async-start
248 (lambda ()
249 (delete-file \"a remote file on a slow link\" nil))
250 'ignore)
251
252 Note: Even when FINISH-FUNC is present, a future is still
253 returned except that it yields no value (since the value is
254 passed to FINISH-FUNC). Call `async-get' on such a future always
255 returns nil. It can still be useful, however, as an argument to
256 `async-ready' or `async-wait'."
257 (require 'find-func)
258 (let ((procvar (make-symbol "proc")))
259 `(let* ((sexp ,start-func)
260 (,procvar
261 (async-start-process
262 "emacs" (expand-file-name invocation-name
263 invocation-directory)
264 ,finish-func
265 "-Q" "-l" ,(find-library-name "async")
266 "-batch" "-f" "async-batch-invoke"
267 ,@(unless async-send-over-pipe
268 '((with-temp-buffer
269 (async--insert-sexp (list 'quote sexp))
270 (buffer-string)))))))
271 ,@(if async-send-over-pipe
272 `((async--transmit-sexp ,procvar (list 'quote sexp))))
273 ,procvar)))
274
275 (defun async-test-1 ()
276 (interactive)
277 (message "Starting async-test-1...")
278 (async-start
279 ;; What to do in the child process
280 (lambda ()
281 (message "This is a test")
282 (sleep-for 3)
283 222)
284
285 ;; What to do when it finishes
286 (lambda (result)
287 (message "Async process done, result should be 222: %s" result)))
288 (message "Starting async-test-1...done"))
289
290 (defun async-test-2 ()
291 (interactive)
292 (message "Starting async-test-2...")
293 (let ((proc (async-start
294 ;; What to do in the child process
295 (lambda ()
296 (message "This is a test")
297 (sleep-for 3)
298 222))))
299 (message "I'm going to do some work here")
300 ;; ....
301 (message "Async process done, result should be 222: %s"
302 (async-get proc))))
303
304 (defun async-test-3 ()
305 (interactive)
306 (message "Starting async-test-3...")
307 (async-start
308 ;; What to do in the child process
309 (lambda ()
310 (message "This is a test")
311 (sleep-for 3)
312 (error "Error in child process")
313 222)
314
315 ;; What to do when it finishes
316 (lambda (result)
317 (message "Async process done, result should be 222: %s" result)))
318 (message "Starting async-test-1...done"))
319
320 (defun async-test-4 ()
321 (interactive)
322 (message "Starting async-test-4...")
323 (async-start-process "sleep" "sleep"
324 ;; What to do when it finishes
325 (lambda (proc)
326 (message "Sleep done, exit code was %d"
327 (process-exit-status proc)))
328 "3")
329 (message "Starting async-test-4...done"))
330
331 (defun async-test-5 ()
332 (interactive)
333 (message "Starting async-test-5...")
334 (let ((proc
335 (async-start
336 ;; What to do in the child process
337 (lambda ()
338 (message "This is a test, sending message")
339 (async-send :hello "world")
340 ;; wait for a message
341 (let ((msg (async-receive)))
342 (message "Child got message: %s"
343 (plist-get msg :goodbye)))
344 (sleep-for 3)
345 222)
346
347 ;; What to do when it finishes
348 (lambda (result)
349 (if (async-message-p result)
350 (message "Got hello from child process: %s"
351 (plist-get result :hello))
352 (message "Async process done, result should be 222: %s"
353 result))))))
354 (async-send proc :goodbye "everyone"))
355 (message "Starting async-test-5...done"))
356
357 (defun async-test-6 ()
358 (interactive)
359 (message "Starting async-test-6...")
360 (async-start
361 ;; What to do in the child process
362 `(lambda ()
363 ,(async-inject-variables "\\`user-mail-address\\'")
364 (format "user-mail-address = %s" user-mail-address))
365
366 ;; What to do when it finishes
367 (lambda (result)
368 (message "Async process done: %s" result))))
369
370 (provide 'async)
371
372 ;;; async.el ends here