Commit | Line | Data |
---|---|---|
be8d412c | 1 | ;;; type-break.el --- encourage rests from typing at appropriate intervals |
458401b6 | 2 | |
458401b6 NF |
3 | ;;; Copyright (C) 1994 Noah S. Friedman |
4 | ||
5 | ;;; Author: Noah Friedman <friedman@prep.ai.mit.edu> | |
458401b6 | 6 | ;;; Maintainer: friedman@prep.ai.mit.edu |
4cf64c15 | 7 | ;;; Keywords: extensions, timers |
be8d412c | 8 | ;;; Status: known to work in GNU Emacs 19.25 or later. |
458401b6 NF |
9 | ;;; Created: 1994-07-13 |
10 | ||
4cf64c15 NF |
11 | ;;; LCD Archive Entry: |
12 | ;;; type-break|Noah Friedman|friedman@prep.ai.mit.edu| | |
be8d412c | 13 | ;;; encourage rests from typing at appropriate intervals| |
4cf64c15 NF |
14 | ;;; $Date$|$Revision$|| |
15 | ||
458401b6 NF |
16 | ;;; $Id$ |
17 | ||
18 | ;;; This program is free software; you can redistribute it and/or modify | |
19 | ;;; it under the terms of the GNU General Public License as published by | |
20 | ;;; the Free Software Foundation; either version 2, or (at your option) | |
21 | ;;; any later version. | |
22 | ;;; | |
23 | ;;; This program is distributed in the hope that it will be useful, | |
24 | ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
25 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
26 | ;;; GNU General Public License for more details. | |
27 | ;;; | |
28 | ;;; You should have received a copy of the GNU General Public License | |
29 | ;;; along with this program; if not, you can either send email to this | |
30 | ;;; program's maintainer or write to: The Free Software Foundation, | |
31 | ;;; Inc.; 675 Massachusetts Avenue; Cambridge, MA 02139, USA. | |
32 | ||
33 | ;;; Commentary: | |
be8d412c NF |
34 | |
35 | ;;; The docstring for the function `type-break-mode' summarizes most of the | |
36 | ;;; details of the interface. | |
37 | ||
38 | ;;; This package relies on the assumption that you live entirely in emacs, | |
39 | ;;; as the author does. If that's not the case for you (e.g. you often | |
40 | ;;; suspend emacs or work in other windows) then this won't help very much; | |
41 | ;;; it will depend on just how often you switch back to emacs. At the very | |
42 | ;;; least, you will want to turn off the keystroke thresholds and rest | |
43 | ;;; interval tracking. | |
44 | ||
45 | ;;; Setting type-break-good-rest-interval makes emacs cons like a maniac | |
46 | ;;; because of repeated calls to `current-time'. There's not really any | |
47 | ;;; good way to avoid this without disabling the variable. | |
48 | ||
49 | ;;; This package was inspired by Roland McGrath's hanoi-break.el. | |
50 | ||
458401b6 NF |
51 | ;;; Code: |
52 | ||
53 | \f | |
622aca7c RM |
54 | (require 'timer) |
55 | ||
be8d412c NF |
56 | ;; Make this nil initially so that the call to type-break-mode at the end |
57 | ;; will cause scheduling and so forth to happen. | |
622aca7c | 58 | ;;;###autoload |
be8d412c | 59 | (defvar type-break-mode nil |
4cf64c15 NF |
60 | "*Non-`nil' means typing break mode is enabled. |
61 | See the docstring for the `type-break-mode' command for more information.") | |
62 | ||
63 | ;;;###autoload | |
64 | (defvar type-break-interval (* 60 60) | |
65 | "*Number of seconds between scheduled typing breaks.") | |
458401b6 | 66 | |
be8d412c NF |
67 | ;;;###autoload |
68 | (defvar type-break-good-rest-interval (/ type-break-interval 6) | |
69 | "*Number of seconds of idle time considered to be an adequate typing rest. | |
70 | ||
71 | When this variable is non-`nil', emacs checks the idle time between | |
72 | keystrokes. If this idle time is long enough to be considered a "good" | |
73 | rest from typing, then the next typing break is simply rescheduled for later. | |
74 | ||
75 | The user will also be admonished if a forced break isn't at least as long | |
76 | as this time, to remind them to rest longer next time.") | |
77 | ||
458401b6 | 78 | ;;;###autoload |
4cf64c15 | 79 | (defvar type-break-query-interval 60 |
458401b6 NF |
80 | "*Number of seconds between queries to take a break, if put off. |
81 | The user will continue to be prompted at this interval until he or she | |
82 | finally submits to taking a typing break.") | |
83 | ||
458401b6 | 84 | ;;;###autoload |
4cf64c15 NF |
85 | (defvar type-break-keystroke-threshold |
86 | ;; Assuming average typing speed is 45wpm and the average word length is | |
87 | ;; about 5 letters, default upper threshold to the average number of | |
88 | ;; keystrokes one is likely to type in a break interval. That way if the | |
89 | ;; user goes through a furious burst of typing activity, cause a typing | |
90 | ;; break to be required sooner than originally scheduled. | |
91 | ;; Conversely, the minimum threshold should be about a quarter of this. | |
92 | (let* ((wpm 45) | |
93 | (avg-word-length 5) | |
94 | (upper (* wpm avg-word-length (/ type-break-interval 60))) | |
95 | (lower (/ upper 4))) | |
96 | (cons lower upper)) | |
97 | "*Upper and lower bound on number of keystrokes for considering typing break. | |
be8d412c | 98 | This structure is a pair of numbers. |
4cf64c15 | 99 | |
be8d412c NF |
100 | The first number is the minimum number of keystrokes that must have been |
101 | entered since the last typing break before considering another one, even if | |
102 | the scheduled time has elapsed; the break is simply rescheduled until later | |
103 | if the minimum threshold hasn't been reached. If this first value is nil, | |
104 | then there is no minimum threshold; as soon as the scheduled time has | |
105 | elapsed, the user will always be queried. | |
4cf64c15 NF |
106 | |
107 | The second number is the maximum number of keystrokes that can be entered | |
108 | before a typing break is requested immediately, pre-empting the originally | |
be8d412c NF |
109 | scheduled break. If this second value is nil, then no pre-emptive breaks |
110 | will occur; only scheduled ones will. | |
4cf64c15 NF |
111 | |
112 | Keys with bucky bits (shift, control, meta, etc) are counted as only one | |
113 | keystroke even though they really require multiple keys to generate them.") | |
be8d412c | 114 | |
458401b6 | 115 | ;;;###autoload |
4cf64c15 NF |
116 | (defvar type-break-query-function 'yes-or-no-p |
117 | "*Function to use for making query for a typing break. | |
118 | It should take a string as an argument, the prompt. | |
119 | Usually this should be set to `yes-or-no-p' or `y-or-n-p'.") | |
120 | ||
458401b6 | 121 | (defvar type-break-demo-function-vector |
4cf64c15 | 122 | [type-break-demo-life type-break-demo-hanoi] |
458401b6 NF |
123 | "*Vector consisting of functions to run as demos during typing breaks. |
124 | When a typing break begins, one of these functions is selected randomly | |
125 | to have emacs do something interesting. | |
622aca7c | 126 | |
458401b6 NF |
127 | Any function in this vector should start a demo which ceases as soon as a |
128 | key is pressed.") | |
622aca7c | 129 | |
4cf64c15 | 130 | ;; These are internal variables. Do not set them yourself. |
622aca7c | 131 | |
4cf64c15 NF |
132 | ;; Non-nil when a scheduled typing break is due. |
133 | (defvar type-break-alarm-p nil) | |
458401b6 | 134 | |
be8d412c NF |
135 | (defvar type-break-keystroke-count 0) |
136 | ||
137 | (defvar type-break-time-last-break nil) | |
138 | (defvar type-break-time-next-break nil) | |
139 | (defvar type-break-time-last-command (current-time)) | |
140 | ||
458401b6 | 141 | \f |
be8d412c NF |
142 | ;; Compute the difference, in seconds, between a and b, two structures |
143 | ;; similar to those returned by `current-time'. | |
144 | ;; Use addition rather than logand since I found it convenient to add | |
145 | ;; seconds to the cdr of some of my stored time values, which may throw off | |
146 | ;; the number of bits in the cdr. | |
147 | (defsubst type-break-time-difference (a b) | |
148 | (+ (lsh (- (car b) (car a)) 16) | |
149 | (- (car (cdr b)) (car (cdr a))))) | |
150 | ||
4cf64c15 NF |
151 | ;;;###autoload |
152 | (defun type-break-mode (&optional prefix) | |
153 | "Enable or disable typing-break mode. | |
154 | This is a minor mode, but it is global to all buffers by default. | |
155 | ||
156 | When this mode is enabled, the user is encouraged to take typing breaks at | |
157 | appropriate intervals; either after a specified amount of time or when the | |
158 | user has exceeded a keystroke threshold. When the time arrives, the user | |
159 | is asked to take a break. If the user refuses at that time, emacs will ask | |
160 | again in a short period of time. The idea is to give the user enough time | |
161 | to find a good breaking point in his or her work, but be sufficiently | |
162 | annoying to discourage putting typing breaks off indefinitely. | |
163 | ||
164 | Calling this command with no prefix argument toggles this mode. | |
165 | A negative prefix argument disables this mode. | |
166 | A non-negative prefix argument or any other non-`nil' argument enables it. | |
167 | ||
168 | The user may enable or disable this mode by setting the variable of the | |
169 | same name, though setting it in that way doesn't reschedule a break or | |
170 | reset the keystroke counter. | |
171 | ||
be8d412c NF |
172 | If the mode was previously disabled and is enabled as a consequence of |
173 | calling this function, it schedules a break with `type-break-schedule' to | |
174 | make sure one occurs (the user can call that command to reschedule the | |
175 | break at any time). It also initializes the keystroke counter. | |
4cf64c15 NF |
176 | |
177 | The variable `type-break-interval' specifies the number of seconds to | |
178 | schedule between regular typing breaks. This variable doesn't directly | |
179 | affect the time schedule; it simply provides a default for the | |
180 | `type-break-schedule' command. | |
181 | ||
182 | The variable `type-break-query-interval' specifies the number of seconds to | |
183 | schedule between repeated queries for breaks when the user answers \"no\" | |
184 | to the previous query. | |
185 | ||
be8d412c NF |
186 | The variable `type-break-good-rest-interval' specifies the minimum amount |
187 | of time which is considered a reasonable typing break. Whenever that time | |
188 | has elapsed, typing breaks are automatically rescheduled for later even if | |
189 | emacs didn't prompt you to take one first. You can disable this behavior. | |
190 | ||
191 | The variable `type-break-keystroke-threshold' is used to determine the | |
192 | thresholds at which typing breaks should be considered. You can use | |
193 | the command `type-break-guestimate-keystroke-threshold' to try to | |
194 | approximate good values for this. | |
4cf64c15 NF |
195 | |
196 | The variable `type-break-query-function' should contain a function (or the | |
197 | symbolic name of a function) to be used to query the user for typing | |
be8d412c NF |
198 | breaks. |
199 | ||
200 | Finally, the command `type-break-statistics' prints interesting things." | |
4cf64c15 NF |
201 | (interactive "P") |
202 | ;; make sure it's there. | |
203 | (add-hook 'post-command-hook 'type-break-check 'append) | |
204 | ||
be8d412c NF |
205 | (let ((already-enabled type-break-mode)) |
206 | (cond | |
207 | ((null prefix) | |
208 | (setq type-break-mode (not type-break-mode))) | |
209 | ((numberp (prefix-numeric-value prefix)) | |
210 | (setq type-break-mode (>= (prefix-numeric-value prefix) 0))) | |
211 | (prefix | |
212 | (setq type-break-mode t)) | |
213 | (t | |
214 | (setq type-break-mode nil))) | |
215 | ||
216 | (cond | |
217 | ((and already-enabled type-break-mode) | |
218 | (and (interactive-p) | |
219 | (message "type-break-mode was already enabled"))) | |
220 | (type-break-mode | |
221 | (setq type-break-keystroke-count 0) | |
222 | (type-break-schedule) | |
223 | (and (interactive-p) | |
224 | (message "type-break-mode is enabled and reset"))) | |
225 | ((interactive-p) | |
226 | (message "type-break-mode is disabled")))) | |
4cf64c15 NF |
227 | type-break-mode) |
228 | ||
622aca7c | 229 | ;;;###autoload |
458401b6 NF |
230 | (defun type-break () |
231 | "Take a typing break. | |
232 | ||
4cf64c15 NF |
233 | During the break, a demo selected from the functions listed in |
234 | `type-break-demo-function-vector' is run. | |
458401b6 | 235 | |
4cf64c15 NF |
236 | After the typing break is finished, the next break is scheduled |
237 | as per the function `type-break-schedule', and the keystroke counter is | |
238 | reset." | |
622aca7c | 239 | (interactive) |
be8d412c | 240 | (setq type-break-time-last-break (current-time)) |
622aca7c | 241 | (save-window-excursion |
4cf64c15 NF |
242 | ;; Eat the screen. |
243 | (and (eq (selected-window) (minibuffer-window)) | |
244 | (other-window 1)) | |
245 | (delete-other-windows) | |
246 | (scroll-right (window-width)) | |
be8d412c | 247 | (message "Press any key to resume from typing break.") |
4cf64c15 NF |
248 | |
249 | (random t) | |
250 | (let* ((len (length type-break-demo-function-vector)) | |
251 | (idx (random len)) | |
252 | (fn (aref type-break-demo-function-vector idx))) | |
253 | (condition-case () | |
254 | (funcall fn) | |
be8d412c NF |
255 | (error nil)))) |
256 | ||
257 | (and type-break-good-rest-interval | |
258 | (< (type-break-time-difference type-break-time-last-command | |
259 | (current-time)) | |
260 | type-break-good-rest-interval) | |
261 | (message "That typing break wasn't really long enough. Rest more next time.")) | |
4cf64c15 | 262 | |
be8d412c NF |
263 | (setq type-break-keystroke-count 0) |
264 | (type-break-schedule)) | |
622aca7c | 265 | |
458401b6 | 266 | \f |
622aca7c | 267 | ;;;###autoload |
458401b6 | 268 | (defun type-break-schedule (&optional time) |
4cf64c15 NF |
269 | "Schedule a typing break for TIME seconds from now. |
270 | If time is not specified, default to `type-break-interval'." | |
622aca7c RM |
271 | (interactive (list (and current-prefix-arg |
272 | (prefix-numeric-value current-prefix-arg)))) | |
458401b6 NF |
273 | (or time (setq time type-break-interval)) |
274 | ;; Remove any old scheduled break | |
4cf64c15 | 275 | (type-break-cancel-schedule) |
be8d412c NF |
276 | (run-at-time time nil 'type-break-alarm) |
277 | ||
278 | (setq type-break-time-next-break (current-time)) | |
279 | (setcar (cdr type-break-time-next-break) | |
280 | (+ time (car (cdr type-break-time-next-break))))) | |
622aca7c | 281 | |
4cf64c15 NF |
282 | (defun type-break-cancel-schedule () |
283 | "Cancel scheduled typing breaks. | |
284 | This does not prevent queries for typing breaks when the keystroke | |
285 | threshold has been reached; to turn off typing breaks altogether, turn off | |
286 | type-break-mode." | |
622aca7c | 287 | (interactive) |
458401b6 | 288 | (let ((timer-dont-exit t)) |
4cf64c15 | 289 | (cancel-function-timers 'type-break-alarm)) |
be8d412c NF |
290 | (setq type-break-alarm-p nil) |
291 | (setq type-break-time-next-break nil)) | |
458401b6 | 292 | |
4cf64c15 NF |
293 | (defun type-break-alarm () |
294 | "This function is run when a scheduled typing break is due." | |
295 | (setq type-break-alarm-p t)) | |
458401b6 NF |
296 | |
297 | (defun type-break-check () | |
4cf64c15 NF |
298 | "Ask to take a typing break if appropriate. |
299 | This may be the case either because the scheduled time has come \(and the | |
300 | minimum keystroke threshold has been reached\) or because the maximum | |
301 | keystroke threshold has been exceeded." | |
458401b6 | 302 | (cond |
4cf64c15 | 303 | (type-break-mode |
be8d412c NF |
304 | (let* ((threshold-pair (and (consp type-break-keystroke-threshold) |
305 | type-break-keystroke-threshold)) | |
306 | (min-threshold (car threshold-pair)) | |
307 | (max-threshold (cdr threshold-pair))) | |
308 | ||
309 | ;; Reset schedule and keystroke count if user has been idle longer | |
310 | ;; than a normal resting period. | |
311 | (cond | |
312 | (type-break-good-rest-interval | |
313 | (and (> (type-break-time-difference type-break-time-last-command | |
314 | (current-time)) | |
315 | type-break-good-rest-interval) | |
316 | (progn | |
317 | (setq type-break-keystroke-count 0) | |
318 | (type-break-schedule))) | |
319 | (setq type-break-time-last-command (current-time)))) | |
320 | ||
321 | (and threshold-pair | |
4cf64c15 NF |
322 | (setq type-break-keystroke-count |
323 | (+ type-break-keystroke-count (length (this-command-keys))))) | |
be8d412c | 324 | |
4cf64c15 NF |
325 | (cond |
326 | ((input-pending-p)) | |
4cf64c15 NF |
327 | (type-break-alarm-p |
328 | (cond | |
be8d412c | 329 | ((and min-threshold |
4cf64c15 NF |
330 | (< type-break-keystroke-count min-threshold))) |
331 | (t | |
be8d412c NF |
332 | ;; If the keystroke count is within min-threshold characters of |
333 | ;; the maximum threshold, set the count to min-threshold. That | |
334 | ;; way, if the count was really close the threshold and the user | |
335 | ;; doesn't choose to take a break now, s/he won't be pestered | |
336 | ;; almost immediately after saying "no"; that's what the query | |
337 | ;; interval delay is for. | |
338 | ;; On the other hand, don't set it too small (make it at least | |
339 | ;; min-threshold); that way we can be sure the user will be asked | |
340 | ;; again to take a break after the query interval has elapsed. | |
341 | ;; If the user chooses to take a break now, the break function | |
342 | ;; will reset the keystroke count anyway. | |
343 | (and max-threshold | |
344 | min-threshold | |
6b62b567 | 345 | (< (- max-threshold type-break-keystroke-count) min-threshold) |
be8d412c NF |
346 | (setq type-break-keystroke-count min-threshold)) |
347 | (type-break-query)))) | |
348 | ((and max-threshold | |
349 | (> type-break-keystroke-count max-threshold)) | |
350 | ;; Set it to the min threshold if possible, to be sure the user | |
351 | ;; will be pestered again in at least a minute. | |
352 | (setq type-break-keystroke-count (or min-threshold 0)) | |
353 | (type-break-query))))))) | |
4cf64c15 NF |
354 | |
355 | (defun type-break-query () | |
356 | (condition-case () | |
357 | (cond | |
358 | ((funcall type-break-query-function "Take a break from typing now? ") | |
359 | (type-break)) | |
360 | (t | |
361 | (type-break-schedule type-break-query-interval))) | |
362 | (quit | |
363 | (type-break-schedule type-break-query-interval)))) | |
458401b6 NF |
364 | |
365 | \f | |
366 | ;; This is a wrapper around hanoi that calls it with an arg large enough to | |
367 | ;; make the largest discs possible that will fit in the window. | |
368 | ;; Also, clean up the *Hanoi* buffer after we're done. | |
4cf64c15 | 369 | (defun type-break-demo-hanoi () |
458401b6 NF |
370 | "Take a hanoiing typing break." |
371 | (and (get-buffer "*Hanoi*") | |
372 | (kill-buffer "*Hanoi*")) | |
373 | (condition-case () | |
374 | (progn | |
375 | (hanoi (/ (window-width) 8)) | |
376 | ;; Wait for user to come back. | |
377 | (read-char) | |
378 | (kill-buffer "*Hanoi*")) | |
be8d412c NF |
379 | (quit |
380 | ;; eat char | |
381 | (read-char) | |
458401b6 NF |
382 | (and (get-buffer "*Hanoi*") |
383 | (kill-buffer "*Hanoi*"))))) | |
384 | ||
385 | ;; This is a wrapper around life that calls it with a `sleep' arg to make | |
386 | ;; it run a little more leisurely. | |
387 | ;; Also, clean up the *Life* buffer after we're done. | |
4cf64c15 | 388 | (defun type-break-demo-life () |
458401b6 NF |
389 | "Take a typing break and get a life." |
390 | (and (get-buffer "*Life*") | |
391 | (kill-buffer "*Life*")) | |
392 | (condition-case () | |
393 | (progn | |
394 | (life 3) | |
395 | ;; Wait for user to come back. | |
396 | (read-char) | |
397 | (kill-buffer "*Life*")) | |
be8d412c | 398 | (quit |
458401b6 NF |
399 | (and (get-buffer "*Life*") |
400 | (kill-buffer "*Life*"))))) | |
401 | ||
402 | \f | |
be8d412c NF |
403 | ;;;###autoload |
404 | (defun type-break-statistics () | |
405 | "Print statistics about typing breaks in a temporary buffer. | |
406 | This includes the last time a typing break was taken, when the next one is | |
407 | scheduled, the keystroke thresholds and the current keystroke count, etc." | |
408 | (interactive) | |
409 | (with-output-to-temp-buffer "*Typing Break Statistics*" | |
410 | (princ (format "Typing break statistics\n-----------------------\n | |
411 | Last typing break : %s | |
412 | Next scheduled typing break : %s\n | |
413 | Minimum keystroke threshold : %s | |
414 | Maximum keystroke threshold : %s | |
415 | Current keystroke count : %s" | |
416 | (if type-break-time-last-break | |
417 | (current-time-string type-break-time-last-break) | |
418 | "never") | |
419 | (if (and type-break-mode type-break-time-next-break) | |
6b62b567 | 420 | (format "%s\t(%s from now)" |
be8d412c | 421 | (current-time-string type-break-time-next-break) |
6b62b567 NF |
422 | (let* ((secs (type-break-time-difference |
423 | (current-time) | |
424 | type-break-time-next-break)) | |
425 | (mins (/ secs 60))) | |
426 | (if (> mins 0) | |
427 | (format "%d minutes" mins) | |
428 | (format "%d seconds" secs)))) | |
be8d412c NF |
429 | "none scheduled") |
430 | (or (car type-break-keystroke-threshold) "none") | |
431 | (or (cdr type-break-keystroke-threshold) "none") | |
432 | type-break-keystroke-count)))) | |
433 | ||
434 | ;;;###autoload | |
435 | (defun type-break-guestimate-keystroke-threshold (wpm &optional wordlen frac) | |
436 | "Guess values for the minimum/maximum keystroke threshold for typing breaks. | |
437 | If called interactively, the user is prompted for their guess as to how | |
438 | many words per minute they usually type. From that, the command sets the | |
439 | values in `type-break-keystroke-threshold' based on a fairly simple | |
440 | algorithm involving assumptions about the average length of words (5). | |
441 | For the minimum threshold, it uses about a quarter of the computed maximum | |
442 | threshold. | |
443 | ||
444 | When called from lisp programs, the optional args WORDLEN and FRAC can be | |
445 | used to override the default assumption about average word length and the | |
446 | fraction of the maximum threshold to which to set the minimum threshold. | |
447 | FRAC should be the inverse of the fractional value; for example, a value of | |
448 | 2 would mean to use one half, a value of 4 would mean to use one quarter, etc." | |
449 | (interactive "nHow many words per minute do you type? ") | |
450 | (let* ((upper (* wpm (or wordlen 5) (/ type-break-interval 60))) | |
451 | (lower (/ upper (or frac 4)))) | |
452 | (or type-break-keystroke-threshold | |
453 | (setq type-break-keystroke-threshold (cons nil nil))) | |
454 | (setcar type-break-keystroke-threshold lower) | |
455 | (setcdr type-break-keystroke-threshold upper) | |
456 | (if (interactive-p) | |
457 | (message "min threshold: %d\tmax threshold: %d" lower upper) | |
458 | type-break-keystroke-threshold))) | |
459 | ||
460 | \f | |
458401b6 NF |
461 | (provide 'type-break) |
462 | ||
4cf64c15 | 463 | (type-break-mode t) |
622aca7c | 464 | |
458401b6 | 465 | ;;; type-break.el ends here |