]> code.delx.au - gnu-emacs-elpa/blob - packages/timerfunctions/timerfunctions.el
Add 'packages/timerfunctions/' from commit 'f0a06654092bcd4ccbcceb9566673e6dd8b01e9e'
[gnu-emacs-elpa] / packages / timerfunctions / timerfunctions.el
1 ;;; timerfunctions.el---enhanced versions of some timer.el functions.
2 ;; Time-stamp: <2011-10-04 21:58:10 deego>
3 ;; Copyright (C) Deepak Goel 2000, 2001, 2002
4 ;; Emacs Lisp Archive entry
5 ;; Filename: timerfunctions.el
6 ;; Author: Deepak Goel <deego@gnufans.org>
7 ;; Version: 1.4.2
8 ;; Created: 2000/11/20
9
10 ;; Author's homepage: http://gnufans.net/~deego
11 ;; For latest version:
12
13 (defconst timerfunctions-home-page
14 "http://gnufans.net/~deego/emacspub/timerfunctions")
15
16
17
18 (defvar timerfunctions-version "1.4.2")
19
20
21 ;;;========================================================
22 ;;;========================================================
23 ;;; Commentary: The latest version can always be downloaded from
24 ;;; http://www.glue.umd.edu/~deego/emacs.html
25
26
27
28 ;; This file is NOT (yet) part of GNU Emacs.
29
30 ;; This is free software; you can redistribute it and/or modify
31 ;; it under the terms of the GNU General Public License as published by
32 ;; the Free Software Foundation; either version 2, or (at your option)
33 ;; any later version.
34
35 ;; This is distributed in the hope that it will be useful,
36 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
37 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38 ;; GNU General Public License for more details.
39
40 ;; You should have received a copy of the GNU General Public License
41 ;; along with GNU Emacs; see the file COPYING. If not, write to the
42 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
43 ;; Boston, MA 02111-1307, USA.
44
45
46
47 ;;; See also: midnight.el (part of emacs), timer.el
48
49
50
51
52 ;;; New features:
53 (defconst timerfunctions-new-features
54 "New since last posting: Changed the syntax of `tf-with-timeout' and
55 provided a `tf-with-timeout-check'.")
56
57 (defun timerfunctions-new-features ()
58 "Provides electric help from variable `timerfunctions-new-features'."
59 (interactive)
60 (with-electric-help
61 '(lambda () (insert timerfunctions-new-features) nil) "*doc*"))
62
63
64 (defconst timerfunctions-introduction
65 "timerfunctions.el contains some 'enhanced' versions of a few timer.el
66 functions. It is also used by vel.el, idledo.el etc.
67
68 Suppose you want emacs to run an action every REDOSECS for
69 _as_long_as emacs remains idle. `tf-run-with-idle-timer' allows that.
70
71 `tf-with-timeout' is a generalized with-timeout where you can inhibit
72 breaks within parts of the body that you want.
73
74 QUICKSTART:
75 Place this file somewhere in yr emacs-load-path, and add the
76 foll. to your .emacs: (load \"timerfunctions.el\")
77 "
78 )
79
80 ;;;###autoload
81 (defun timerfunctions-introduction ()
82 "Provides electric help from variable `timerfunctions-introduction'."
83 (interactive)
84 (with-electric-help
85 '(lambda () (insert timerfunctions-introduction) nil) "*doc*"))
86
87 ;;; Real Code:
88
89
90 ;;;###autoload
91 (defun tf-time-difference (timeplus timesub)
92 "Gives the time in seconds elaspsed from TIMESUB to TIMEPLUS.
93 Almost like \(- TIMEPLUS TIMESUB \)."
94 (+ (* (expt 2 16) (- (car timeplus) (car timesub)))
95 (- (cadr timeplus) (cadr timesub)))
96 )
97
98
99 ;;;###autoload
100 (defun tf-run-with-idle-timer (secs repeat redosecs redorepeat includeruntime function &rest args)
101 "Args are SECS, REPEAT, REDOSECS, REDOREPEAT, INCLUDERUNTIME,
102 FUNCTION and &rest ARGS.
103 Similar to run-with-idle-timer, except that provides more options.
104 Suppose you want emacs to run an action every REDOSECS for as long as
105 emacs remains idle. Think you can do it with the emacs'
106 run-with-idle-timer? Think again.. :) That function will perform the
107 action exactly once every time emacs goes idle. This funciton,
108 tf-run-with-idle-timer *will* allow you to keep performing an action
109 as long as emacs remains idle.
110
111 SECS is the number of seconds to wait once emacs has first gone
112 idle. It can really be any expression whose at runtime yields a
113 number.. Note that the way run-with-idle-timer is defined, SECS will
114 unfortunately be evalled immediately after you call this function, but
115 redosecs will be *every* time emacs *remains* idle..yay..
116
117
118 If REDOREPEAT is non-nil, the action is repeated as long emacs remains
119 idle. REDOSECS is the number of additional seconds (after the action
120 has been done) to wait if emacs remains idle before performing the
121 action again. Again, redosecs does not have to be a number, it can be
122 any expression whose eval yields to a number...
123
124 If INCLUDERUNTIME is non-nil, REDOSECS is the number of
125 additional seconds to wait after the action has been invoked (not
126 finished).
127
128 If REPEAT is nonnil, the entire cycle is repeated every time emacs
129 next goes idle.. (as in the default run-with-idle-timer."
130 (apply 'run-with-idle-timer
131 (eval secs) repeat 'tf-run-while-idle
132 redosecs redorepeat includeruntime
133 function args)
134 )
135
136
137 (defun tf-run-while-idle (redosecs redorepeat includeruntime
138 function &rest args)
139 "Runs FUNCTION with ARGS and optionally repeats if emacs idle.
140 Probably is of no use unless used in programs.
141 If REDOREPEAT is non-nil, the function is repeated periodically every
142 REDOSECS as long as emacs remains idle. By default, emacs waits
143 REDOSECS *after* the function is done executing to repeat. If you want
144 the execution-time to count towards REDOSECS, make INCLUDERUNTIME
145 non-nil.
146 SECS and REDOSECS can be any expressions that eval at runtime to
147 numbers.. In particular, they can simply be numbers..
148
149 "
150 (if (not includeruntime)
151 (progn
152 (apply function args)
153 (if redorepeat
154 (while (sit-for (eval redosecs))
155 (apply function args))))
156 (progn
157 (let ((before-time (current-time)))
158 (apply function args)
159 (if redorepeat
160 (while (sit-for (-
161 (eval redosecs)
162 (tf-time-difference (current-time)
163 before-time)))
164 (setq before-time (current-time))
165 (apply function args))))))
166 )
167
168
169 ;;;====================================================
170 ;;;TESTS FOLLOW
171 (defun tf-test-display-time-internal
172 ()
173 (interactive)
174 (let ((thisbuffer (buffer-name)))
175 (switch-to-buffer-other-window "*scratch*")
176 (goto-char (point-max))
177 (insert (concat "\n" (format "%S" (cadr (current-time)))))
178 (recenter)
179 (switch-to-buffer-other-window thisbuffer))
180 )
181
182
183 (defun tf-test-idle-timer ()
184 "Run this and watch..Play around with the options.. If you run it,
185 you may have to exit your emacs session to restore normal emacs!
186 unless you are an expert, that is.."
187
188 (interactive)
189 (tf-run-with-idle-timer
190 1 t 3 t nil 'tf-test-display-time-internal)
191 )
192
193
194
195
196
197 (defun tf-test-timeout ()
198 "Bad count should be zero. "
199 (interactive)
200 (let ((inhi nil) (goodcount 0) (badcount 0) (ctr 0) (a 1) (b 2)
201 (mytag nil)
202 (myvar nil)
203 )
204 (loop
205 for ctr from 0 to 10 do
206 (message "ctr=%S" ctr)
207 (tf-with-timeout 'inhi 'mytah 'myvar
208 (0.3 nil)
209 (loop for i from 0 to 100000 do
210 (message "ctr=%S, i=%S" ctr i)
211 (setq inhi t)
212 (setq a (random 100))
213 (sleep-for 0.1)
214 (setq b a)
215 (setq inhi nil)
216 (sleep-for 0.02)
217 ))
218 (if (equal b a) (incf goodcount) (incf badcount)))
219 (message "Goodcount: %S; badcount: %S" goodcount badcount)))
220
221
222
223 (defun tf-test-timeout-complex ()
224 "Should return a value of 20000 for a. "
225
226 (interactive)
227 (let ((inhi t) (goodcount 0) (badcount 0) (ctr 0) (a 1) (b 2)
228 (mytag nil)
229 (myvar nil)
230 )
231 (setq a 0)
232 (message "ctr=%S" ctr)
233 (tf-with-timeout
234 'inhi 'mytag 'myvar
235 (0.1 nil)
236 (loop for i from 0 to 10000 do
237 (message "first loop. i=%S" ctr i)
238 (incf a))
239 (message "initial loop ends here.")
240 ;; no throw here because loop prohibited.
241 (tf-with-timeout-check 'inhi 'mytag 'myvar)
242 ;; this shouldn't help either
243 (sit-for 0.3)
244
245 (loop for i from 0 to 10000 do
246 (message "second loop. i=%S" i)
247 (incf a))
248 (message "second loop ends here.")
249 (setq inhi nil)
250 ;; this should throw.
251 (tf-with-timeout-check 'inhi 'mytag 'myvar)
252 ;; this should NOT be needed.
253 ;;(sit-for 0.2)
254 ;; this loop should never take place.
255 (loop for i from 0 to 1000 do
256 (message "third loop, i=%S" i)
257 (incf a))
258 (message "third loop ends here."))
259 (message "%S" a)
260 a))
261
262
263
264 (defvar tf-internal-var-recenter 1)
265 (defun tf-internal-recenter-toggle-my ()
266 (interactive)
267 (recenter tmpp)
268 (setq tf-internal-var-recenter (- 0 tf-internal-var-recenter)))
269
270 (defun tf-example-timer-recenter ()
271 "Changes the screen display every 3 seconds, thus ensuring that you
272 don't time out of ssh sessions."
273 tf-run-with-idle-timer 3 t 3 t nil 'tf-internal-recenter-toggle-my)
274
275
276
277
278 (defun tf-wait-until-idle (&optional secs)
279 "DOES NOT WORK YET. Waits until idle.
280 Will help run processes in background. This function will NOT create
281 a timer. Will simply use sit-for. "
282 (if (null secs)
283 (setq secs 1))
284 (while (not (sit-for secs))
285 (sit-for 1))
286 (message "tf-wait-until-idle DONE WAITING!")
287 )
288
289
290 ;;;Tue Jan 23 17:38:44 2001
291 (defmacro tf-ignore-errors (&rest body)
292 "Like ignore-errors, but tells the error.."
293 (let ((err (gensym)))
294 (list 'condition-case err (cons 'progn body)
295 (list 'error
296 (list 'message
297 (list 'concat
298 "IGNORED ERROR: "
299 (list 'error-message-string err)))))
300 ))
301
302
303
304
305 (defvar tf-with-timeout-repeat-sec 0.01
306 "If the initial timeout fails because of inhibitedness, we shall
307 check every this many seconds to see if we are uninhibited. This
308 variable is customizable. ")
309
310
311 (defun tf-with-timeout-handler-internal (tag timedoutvar inhibitp)
312 (set timedoutvar t)
313 ;;(tf-with-timeout-check tag timedoutvar inhibitp)
314 ;; which is equivalent to:
315 (unless (eval inhibitp)
316 (tf-ignore-errors (throw tag 'timeout)))
317 )
318
319 (defun tf-with-timeout-check (inhibitp tag timedoutvar)
320 ;; check whether timeout has actually reached.
321 ;; we need this step because this function might be called by the
322 ;; user as well.
323 (when (eval timedoutvar)
324 (unless (eval inhibitp)
325 (tf-ignore-errors (throw tag 'timeout)))))
326
327
328
329 (defvar tf-tag-tmpvar nil)
330
331 (defmacro tf-catch (tag &rest body)
332 `(let
333 ;; unquote the tag here..
334 ((,(cadr tag) 'tf-catch))
335 (catch ,tag
336 ,@body)))
337
338 (defmacro tf-throw (tag value)
339 `(when (eql (eval ,tag) 'tf-catch)
340 (throw ,tag value)))
341
342
343 ;;;###autoload
344 (defmacro tf-with-timeout (inhibitp timertag timedoutvar tlist &rest body)
345 "Like `with-timeout' but provide ability to inhibit timeout during
346 parts of the body. Note that most of the time, you may not need this
347 functionality at all unless you want to be very 'clean' about
348 things---you could get by with the regular with-timeout and not using
349 sit-for's in the body. Or with the regular with-timeout and using
350 unwind-protect.
351
352
353 TO DECIDE: IN VIEW OF THE UNWIND-PROTECT, DO WE NEED THIS FUNCTION AT ALL??
354
355 Run BODY, but if it doesn't finish in SECONDS seconds, give up.
356 If we give up, we run the TIMEOUT-FORMS which are contained in TLIST
357 and return the value of the last one.
358 The call should look like:
359 (tf-with-timeout quoted-expr (SECONDS TIMEOUT-FORMS...) BODY...)
360
361 The timeout is checked whenever Emacs waits for some kind of external
362 event \(such as keyboard input, input from subprocesses, or a certain time);
363 if the program loops without waiting in any way, the timeout will not
364 be detected. Furthermore:
365
366 During the execution of the body, we SHALL NOT time out when INHIBITP
367 evals to non-nil. Thus, for example, you might initially setq a
368 variable my-var as nil, supply inhibitp as 'my-var, and then you may
369 setq my-var to t or nil within the body of tf-with-timeout to enable
370 or disable timeout. The best use of this functionality is to setq
371 inhibitp to t when during parts of loops where you do not want the
372 body broken within certain parts of the loop. (Of course, if that
373 part of the loop does not contain any sit-for's or read's then you
374 don't have to worry about this in the first place..)
375
376
377 again, Do not forget my-var to some value before attempting to use this
378 tf-with-timeout :)
379
380 Here's an example:
381
382
383 (let ((myinhibit t))
384 (tf-with-timeout 'myinhibit 'mytag 'mytimedoutvar
385 (2 2)
386 (setq a nil)
387 (setq b nil)
388 (sit-for 4)
389 (setq a 4)
390 (setq myinhibit nil)
391 (sit-for 2)
392 (setq b 5)
393 ))
394
395
396 The above example requests a timeout within 2 seconds. However, the
397 timeout can takes place only when myinhibit is set to nil,
398 which becomes true after about 4 seconds. Thus, after the execution of the
399 body, a has the value 4, but b has the value nil.
400
401 See `tf-test-timeout' for another example.
402
403 Important Note: If the body of a loop tends to stay in a timeout
404 inhibited region for most of the time, then make sure that the timeout
405 enabled region atleast spans about 0.02 seconds.. thus, use (sleep-for
406 0.02) if needed.. this is because we check every 0.01 seconds if an
407 uninhibited timeout condition has been satisfied.
408
409 But perhaps you do not want to include (sleep-for 0.02) because that
410 wastes precious cpu time. Simple, don't include it, just after a long
411 inhibited body, you can include a timeout check within the body
412 instead of (sleep-for 0.02):
413 (tf-with-timeout-check 'mytag 'mytimedoutvar 'myinhibitp)
414
415 Moreover, if that is the main check you rely on, you it perhaps makes
416 sense to increase the value of tf-with-timeout-repeat-sec, so that
417 your cpu cycles are not wasted every 0.01 sec. See the doc of that
418 variable for more.
419
420 Timertag should be a quoted symbol, also we WILL set that symbol to t
421 during the execution of these forms.
422
423 "
424 (let ((seconds (car tlist))
425 (timeout-forms (cdr tlist)))
426 `(let (
427 ;;(with-timeout-tag (cons nil nil))
428 with-timeout-value with-timeout-timer)
429 (set ,timedoutvar nil)
430 (if (catch ,timertag
431 (progn
432 (setq with-timeout-timer
433 (run-with-timer ,seconds tf-with-timeout-repeat-sec
434 'tf-with-timeout-handler-internal
435 ,timertag ,timedoutvar
436 ,inhibitp))
437 (setq with-timeout-value (progn ,@body))
438 nil))
439 (progn ,@timeout-forms)
440 (cancel-timer with-timeout-timer)
441 with-timeout-value))))
442
443
444 (provide 'timerfunctions)
445
446 ;;;timerfunctions.el ends here.
447