Sync to HEAD
[bpt/emacs.git] / lisp / type-break.el
CommitLineData
be8d412c 1;;; type-break.el --- encourage rests from typing at appropriate intervals
458401b6 2
6b61353c 3;; Copyright (C) 1994, 95, 97, 2000, 2004 Free Software Foundation, Inc.
99c0333b 4
f6cafb3e
NF
5;; Author: Noah Friedman
6;; Maintainer: Noah Friedman <friedman@splode.com>
99c0333b 7;; Keywords: extensions, timers
846f6dd9 8;; Status: Works in GNU Emacs 19.25 or later, some versions of XEmacs
99c0333b 9;; Created: 1994-07-13
846f6dd9 10
99c0333b
NF
11;; This file is part of GNU Emacs.
12
13;; GNU Emacs is free software; you can redistribute it and/or modify
14;; it under the terms of the GNU General Public License as published by
15;; the Free Software Foundation; either version 2, or (at your option)
16;; any later version.
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
b4dc9e6a
NF
24;; along with GNU Emacs; see the file COPYING. If not, write to the
25;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26;; Boston, MA 02111-1307, USA.
458401b6
NF
27
28;;; Commentary:
be8d412c 29
b4dc9e6a
NF
30;; The docstring for the function `type-break-mode' summarizes most of the
31;; details of the interface.
be8d412c 32
b4dc9e6a
NF
33;; This package relies on the assumption that you live entirely in emacs,
34;; as the author does. If that's not the case for you (e.g. you often
35;; suspend emacs or work in other windows) then this won't help very much;
36;; it will depend on just how often you switch back to emacs. At the very
37;; least, you will want to turn off the keystroke thresholds and rest
38;; interval tracking.
be8d412c 39
846f6dd9
NF
40;; If you prefer not to be queried about taking breaks, but instead just
41;; want to be reminded, do the following:
42;;
43;; (setq type-break-query-mode nil)
44;;
45;; Or call the command `type-break-query-mode' with a negative prefix
46;; argument.
47
48;; If you find echo area messages annoying and would prefer to see messages
49;; in the mode line instead, do M-x type-break-mode-line-message-mode
50;; or set the variable of the same name to `t'.
b4dc9e6a
NF
51
52;; This program can truly cons up a storm because of all the calls to
53;; `current-time' (which always returns 3 fresh conses). I'm dismayed by
54;; this, but I think the health of my hands is far more important than a
55;; few pages of virtual memory.
56
846f6dd9
NF
57;; This program has no hope of working in Emacs 18.
58
b4dc9e6a
NF
59;; This package was inspired by Roland McGrath's hanoi-break.el.
60;; Several people contributed feedback and ideas, including
5762abec 61;; Roland McGrath <roland@gnu.org>
ad953485 62;; Kleanthes Koniaris <kgk@koniaris.com>
5762abec 63;; Mark Ashton <mpashton@gnu.org>
b4dc9e6a 64;; Matt Wilding <wilding@cli.com>
846f6dd9 65;; Robert S. Boyer <boyer@cs.utexas.edu>
be8d412c 66
458401b6
NF
67;;; Code:
68
69\f
104221a0
SE
70(defgroup type-break nil
71 "Encourage the user to take a rest from typing at suitable intervals."
72 :prefix "type-break"
73 :group 'keyboard)
74
622aca7c 75;;;###autoload
104221a0 76(defcustom type-break-mode nil
511a0719 77 "Toggle typing break mode.
104221a0 78See the docstring for the `type-break-mode' command for more information.
4de26885
DL
79Setting this variable directly does not take effect;
80use either \\[customize] or the function `type-break-mode'."
104221a0
SE
81 :set (lambda (symbol value)
82 (type-break-mode (if value 1 -1)))
e18b0c51 83 :initialize 'custom-initialize-default
104221a0
SE
84 :type 'boolean
85 :group 'type-break
e18b0c51 86 :require 'type-break)
4cf64c15
NF
87
88;;;###autoload
104221a0
SE
89(defcustom type-break-interval (* 60 60)
90 "*Number of seconds between scheduled typing breaks."
91 :type 'integer
92 :group 'type-break)
458401b6 93
be8d412c 94;;;###autoload
104221a0 95(defcustom type-break-good-rest-interval (/ type-break-interval 6)
be8d412c
NF
96 "*Number of seconds of idle time considered to be an adequate typing rest.
97
ee6258ff 98When this variable is non-nil, emacs checks the idle time between
cc669dd8 99keystrokes. If this idle time is long enough to be considered a \"good\"
be8d412c
NF
100rest from typing, then the next typing break is simply rescheduled for later.
101
cc669dd8 102If a break is interrupted before this much time elapses, the user will be
104221a0
SE
103asked whether or not really to interrupt the break."
104 :type 'integer
105 :group 'type-break)
be8d412c 106
458401b6 107;;;###autoload
104221a0 108(defcustom type-break-keystroke-threshold
cc669dd8 109 ;; Assuming typing speed is 35wpm (on the average, do you really
f486195c
NF
110 ;; type more than that in a minute? I spend a lot of time reading mail
111 ;; and simply studying code in buffers) and average word length is
4cf64c15
NF
112 ;; about 5 letters, default upper threshold to the average number of
113 ;; keystrokes one is likely to type in a break interval. That way if the
114 ;; user goes through a furious burst of typing activity, cause a typing
115 ;; break to be required sooner than originally scheduled.
f486195c 116 ;; Conversely, the minimum threshold should be about a fifth of this.
cc669dd8 117 (let* ((wpm 35)
4cf64c15
NF
118 (avg-word-length 5)
119 (upper (* wpm avg-word-length (/ type-break-interval 60)))
f486195c 120 (lower (/ upper 5)))
4cf64c15
NF
121 (cons lower upper))
122 "*Upper and lower bound on number of keystrokes for considering typing break.
104221a0 123This structure is a pair of numbers (MIN . MAX).
4cf64c15 124
be8d412c
NF
125The first number is the minimum number of keystrokes that must have been
126entered since the last typing break before considering another one, even if
127the scheduled time has elapsed; the break is simply rescheduled until later
128if the minimum threshold hasn't been reached. If this first value is nil,
129then there is no minimum threshold; as soon as the scheduled time has
130elapsed, the user will always be queried.
4cf64c15
NF
131
132The second number is the maximum number of keystrokes that can be entered
133before a typing break is requested immediately, pre-empting the originally
be8d412c
NF
134scheduled break. If this second value is nil, then no pre-emptive breaks
135will occur; only scheduled ones will.
4cf64c15
NF
136
137Keys with bucky bits (shift, control, meta, etc) are counted as only one
b4dc9e6a
NF
138keystroke even though they really require multiple keys to generate them.
139
140The command `type-break-guesstimate-keystroke-threshold' can be used to
104221a0
SE
141guess a reasonably good pair of values for this variable."
142 :type 'sexp
143 :group 'type-break)
b4dc9e6a 144
104221a0 145(defcustom type-break-query-mode t
ee6258ff 146 "*Non-nil means ask whether or not to prompt user for breaks.
846f6dd9 147If so, call the function specified in the value of the variable
104221a0
SE
148`type-break-query-function' to do the asking."
149 :type 'boolean
150 :group 'type-break)
846f6dd9 151
ad953485
NF
152(defcustom type-break-query-function 'yes-or-no-p
153 "*Function to use for making query for a typing break.
b4dc9e6a
NF
154It should take a string as an argument, the prompt.
155Usually this should be set to `yes-or-no-p' or `y-or-n-p'.
156
ee6258ff 157To avoid being queried at all, set `type-break-query-mode' to nil."
ad953485
NF
158 :type '(radio function
159 (function-item yes-or-no-p)
160 (function-item y-or-n-p))
161 :group 'type-break)
b4dc9e6a 162
104221a0 163(defcustom type-break-query-interval 60
b4dc9e6a
NF
164 "*Number of seconds between queries to take a break, if put off.
165The user will continue to be prompted at this interval until he or she
104221a0
SE
166finally submits to taking a typing break."
167 :type 'integer
168 :group 'type-break)
be8d412c 169
104221a0 170(defcustom type-break-time-warning-intervals '(300 120 60 30)
e7b20417
NF
171 "*List of time intervals for warnings about upcoming typing break.
172At each of the intervals (specified in seconds) away from a scheduled
104221a0
SE
173typing break, print a warning in the echo area."
174 :type '(repeat integer)
175 :group 'type-break)
e7b20417 176
104221a0 177(defcustom type-break-keystroke-warning-intervals '(300 200 100 50)
e7b20417
NF
178 "*List of keystroke measurements for warnings about upcoming typing break.
179At each of the intervals (specified in keystrokes) away from the upper
180keystroke threshold, print a warning in the echo area.
181If either this variable or the upper threshold is set, then no warnings
104221a0
SE
182will occur."
183 :type '(repeat integer)
184 :group 'type-break)
185
104221a0 186(defcustom type-break-warning-repeat 40
e7b20417
NF
187 "*Number of keystrokes for which warnings should be repeated.
188That is, for each of this many keystrokes the warning is redisplayed
104221a0
SE
189in the echo area to make sure it's really seen."
190 :type 'integer
191 :group 'type-break)
e7b20417 192
ad953485
NF
193(defcustom type-break-time-stamp-format "[%H:%M] "
194 "*Timestamp format used to prefix messages.
195Format specifiers are as used by `format-time-string'."
196 :type 'string
197 :group 'type-break)
198
104221a0 199(defcustom type-break-demo-functions
e7b20417 200 '(type-break-demo-boring type-break-demo-life type-break-demo-hanoi)
cc669dd8 201 "*List of functions to consider running as demos during typing breaks.
458401b6
NF
202When a typing break begins, one of these functions is selected randomly
203to have emacs do something interesting.
622aca7c 204
cc669dd8 205Any function in this list should start a demo which ceases as soon as a
104221a0
SE
206key is pressed."
207 :type '(repeat function)
208 :group 'type-break)
622aca7c 209
846f6dd9 210(defvar type-break-post-command-hook '(type-break-check)
b4dc9e6a
NF
211 "Hook run indirectly by post-command-hook for typing break functions.
212This is not really intended to be set by the user, but it's probably
213harmless to do so. Mainly it is used by various parts of the typing break
214program to delay actions until after the user has completed some command.
215It exists because `post-command-hook' itself is inaccessible while its
216functions are being run, and some type-break--related functions want to
217remove themselves after running.")
e7b20417 218
846f6dd9
NF
219\f
220;; Mode line frobs
221
104221a0 222(defcustom type-break-mode-line-message-mode nil
ee6258ff 223 "*Non-nil means put type-break related messages in the mode line.
846f6dd9
NF
224Otherwise, messages typically go in the echo area.
225
104221a0
SE
226See also `type-break-mode-line-format' and its members."
227 :type 'boolean
228 :group 'type-break)
846f6dd9
NF
229
230(defvar type-break-mode-line-format
231 '(type-break-mode-line-message-mode
232 (""
233 type-break-mode-line-break-message
234 type-break-mode-line-warning))
235 "*Format of messages in the mode line concerning typing breaks.")
236
237(defvar type-break-mode-line-break-message
238 '(type-break-mode-line-break-message-p
239 type-break-mode-line-break-string))
240
241(defvar type-break-mode-line-break-message-p nil)
ad953485 242(defvar type-break-mode-line-break-string " *** TAKE A TYPING BREAK NOW ***")
846f6dd9
NF
243
244(defvar type-break-mode-line-warning
245 '(type-break-mode-line-break-message-p
246 ("")
247 (type-break-warning-countdown-string
ad953485
NF
248 (" *** "
249 "Break in "
846f6dd9
NF
250 type-break-warning-countdown-string
251 " "
252 type-break-warning-countdown-string-type
253 "***"))))
254
255(defvar type-break-warning-countdown-string nil
256 "If non-nil, this is a countdown for the next typing break.
257
258This variable, in conjunction with `type-break-warning-countdown-string-type'
b688ed44 259\(which indicates whether this value is a number of keystrokes or seconds)
846f6dd9
NF
260is installed in mode-line-format to notify of imminent typing breaks.")
261
262(defvar type-break-warning-countdown-string-type nil
263 "Indicates the unit type of `type-break-warning-countdown-string'.
264It will be either \"seconds\" or \"keystrokes\".")
265
266\f
4cf64c15 267;; These are internal variables. Do not set them yourself.
622aca7c 268
defa7346 269(defvar type-break-alarm-p nil)
be8d412c 270(defvar type-break-keystroke-count 0)
be8d412c
NF
271(defvar type-break-time-last-break nil)
272(defvar type-break-time-next-break nil)
273(defvar type-break-time-last-command (current-time))
e7b20417
NF
274(defvar type-break-current-time-warning-interval nil)
275(defvar type-break-current-keystroke-warning-interval nil)
276(defvar type-break-time-warning-count 0)
277(defvar type-break-keystroke-warning-count 0)
cc669dd8 278\f
4cf64c15
NF
279;;;###autoload
280(defun type-break-mode (&optional prefix)
281 "Enable or disable typing-break mode.
282This is a minor mode, but it is global to all buffers by default.
283
284When this mode is enabled, the user is encouraged to take typing breaks at
285appropriate intervals; either after a specified amount of time or when the
286user has exceeded a keystroke threshold. When the time arrives, the user
287is asked to take a break. If the user refuses at that time, emacs will ask
288again in a short period of time. The idea is to give the user enough time
289to find a good breaking point in his or her work, but be sufficiently
290annoying to discourage putting typing breaks off indefinitely.
291
4cf64c15 292A negative prefix argument disables this mode.
cc669dd8 293No argument or any non-negative argument enables it.
4cf64c15
NF
294
295The user may enable or disable this mode by setting the variable of the
296same name, though setting it in that way doesn't reschedule a break or
297reset the keystroke counter.
298
be8d412c
NF
299If the mode was previously disabled and is enabled as a consequence of
300calling this function, it schedules a break with `type-break-schedule' to
301make sure one occurs (the user can call that command to reschedule the
302break at any time). It also initializes the keystroke counter.
4cf64c15
NF
303
304The variable `type-break-interval' specifies the number of seconds to
305schedule between regular typing breaks. This variable doesn't directly
306affect the time schedule; it simply provides a default for the
307`type-break-schedule' command.
308
cc669dd8
NF
309If set, the variable `type-break-good-rest-interval' specifies the minimum
310amount of time which is considered a reasonable typing break. Whenever
311that time has elapsed, typing breaks are automatically rescheduled for
312later even if emacs didn't prompt you to take one first. Also, if a break
313is ended before this much time has elapsed, the user will be asked whether
314or not to continue.
be8d412c
NF
315
316The variable `type-break-keystroke-threshold' is used to determine the
317thresholds at which typing breaks should be considered. You can use
b4dc9e6a 318the command `type-break-guesstimate-keystroke-threshold' to try to
be8d412c 319approximate good values for this.
4cf64c15 320
b4dc9e6a
NF
321There are several variables that affect how or when warning messages about
322imminent typing breaks are displayed. They include:
323
2157be68
RS
324 `type-break-mode-line-message-mode'
325 `type-break-time-warning-intervals'
326 `type-break-keystroke-warning-intervals'
327 `type-break-warning-repeat'
328 `type-break-warning-countdown-string'
329 `type-break-warning-countdown-string-type'
b4dc9e6a 330
846f6dd9
NF
331There are several variables that affect if, how, and when queries to begin
332a typing break occur. They include:
b4dc9e6a 333
2157be68
RS
334 `type-break-query-mode'
335 `type-break-query-function'
336 `type-break-query-interval'
b4dc9e6a 337
be8d412c 338Finally, the command `type-break-statistics' prints interesting things."
4cf64c15 339 (interactive "P")
846f6dd9 340 (type-break-check-post-command-hook)
4cf64c15 341
be8d412c 342 (let ((already-enabled type-break-mode))
cc669dd8 343 (setq type-break-mode (>= (prefix-numeric-value prefix) 0))
be8d412c
NF
344
345 (cond
346 ((and already-enabled type-break-mode)
347 (and (interactive-p)
2157be68 348 (message "Type Break mode is already enabled")))
be8d412c 349 (type-break-mode
846f6dd9
NF
350 (or global-mode-string
351 (setq global-mode-string '("")))
7cd3279a
RS
352 (or (assq 'type-break-mode-line-message-mode
353 minor-mode-alist)
354 (setq minor-mode-alist
355 (cons type-break-mode-line-format
356 minor-mode-alist)))
e7b20417 357 (type-break-keystroke-reset)
846f6dd9 358 (type-break-mode-line-countdown-or-break nil)
be8d412c
NF
359 (type-break-schedule)
360 (and (interactive-p)
2157be68 361 (message "Type Break mode is enabled and reset")))
846f6dd9
NF
362 (t
363 (type-break-keystroke-reset)
364 (type-break-mode-line-countdown-or-break nil)
365 (type-break-cancel-schedule)
366 (and (interactive-p)
2157be68 367 (message "Type Break mode is disabled")))))
4cf64c15
NF
368 type-break-mode)
369
846f6dd9
NF
370(defun type-break-mode-line-message-mode (&optional prefix)
371 "Enable or disable warnings in the mode line about typing breaks.
372
373A negative prefix argument disables this mode.
374No argument or any non-negative argument enables it.
375
376The user may also enable or disable this mode simply by setting the
377variable of the same name.
378
379Variables controlling the display of messages in the mode line include:
380
2157be68
RS
381 `mode-line-format'
382 `global-mode-string'
383 `type-break-mode-line-break-message'
384 `type-break-mode-line-warning'"
846f6dd9
NF
385 (interactive "P")
386 (setq type-break-mode-line-message-mode
387 (>= (prefix-numeric-value prefix) 0))
388 (and (interactive-p)
389 (if type-break-mode-line-message-mode
390 (message "type-break-mode-line-message-mode is enabled")
391 (message "type-break-mode-line-message-mode is disabled")))
392 type-break-mode-line-message-mode)
393
394(defun type-break-query-mode (&optional prefix)
395 "Enable or disable warnings in the mode line about typing breaks.
396
397When enabled, the user is periodically queried about whether to take a
398typing break at that moment. The function which does this query is
399specified by the variable `type-break-query-function'.
b4dc9e6a
NF
400
401A negative prefix argument disables this mode.
402No argument or any non-negative argument enables it.
403
404The user may also enable or disable this mode simply by setting the
405variable of the same name."
406 (interactive "P")
846f6dd9
NF
407 (setq type-break-query-mode
408 (>= (prefix-numeric-value prefix) 0))
409 (and (interactive-p)
410 (if type-break-query-mode
411 (message "type-break-query-mode is enabled")
412 (message "type-break-query-mode is disabled")))
413 type-break-query-mode)
b4dc9e6a 414
846f6dd9 415\f
622aca7c 416;;;###autoload
458401b6
NF
417(defun type-break ()
418 "Take a typing break.
419
4cf64c15 420During the break, a demo selected from the functions listed in
cc669dd8 421`type-break-demo-functions' is run.
458401b6 422
4cf64c15 423After the typing break is finished, the next break is scheduled
cc669dd8 424as per the function `type-break-schedule'."
622aca7c 425 (interactive)
ad953485 426 (do-auto-save)
e7b20417 427 (type-break-cancel-schedule)
cc669dd8
NF
428 (let ((continue t)
429 (start-time (current-time)))
430 (setq type-break-time-last-break start-time)
431 (while continue
432 (save-window-excursion
433 ;; Eat the screen.
434 (and (eq (selected-window) (minibuffer-window))
435 (other-window 1))
436 (delete-other-windows)
437 (scroll-right (window-width))
438 (message "Press any key to resume from typing break.")
439
440 (random t)
441 (let* ((len (length type-break-demo-functions))
442 (idx (random len))
443 (fn (nth idx type-break-demo-functions)))
444 (condition-case ()
445 (funcall fn)
446 (error nil))))
be8d412c 447
cc669dd8
NF
448 (cond
449 (type-break-good-rest-interval
450 (let ((break-secs (type-break-time-difference
451 start-time (current-time))))
452 (cond
453 ((>= break-secs type-break-good-rest-interval)
454 (setq continue nil))
b4dc9e6a
NF
455 ;; 60 seconds may be too much leeway if the break is only 3
456 ;; minutes to begin with. You can just say "no" to the query
457 ;; below if you're in that much of a hurry.
458 ;((> 60 (abs (- break-secs type-break-good-rest-interval)))
459 ; (setq continue nil))
e7b20417 460 ((funcall
cc669dd8 461 type-break-query-function
ad953485
NF
462 (format "%sYou really ought to rest %s more. Continue break? "
463 (type-break-time-stamp)
cc669dd8
NF
464 (type-break-format-time (- type-break-good-rest-interval
465 break-secs)))))
466 (t
467 (setq continue nil)))))
468 (t (setq continue nil)))))
4cf64c15 469
e7b20417 470 (type-break-keystroke-reset)
846f6dd9 471 (type-break-mode-line-countdown-or-break nil)
be8d412c 472 (type-break-schedule))
622aca7c 473
458401b6 474\f
458401b6 475(defun type-break-schedule (&optional time)
defa7346
NF
476 "Schedule a typing break for TIME seconds from now.
477If time is not specified, default to `type-break-interval'."
478 (interactive (list (and current-prefix-arg
479 (prefix-numeric-value current-prefix-arg))))
458401b6 480 (or time (setq time type-break-interval))
846f6dd9 481 (type-break-check-post-command-hook)
4cf64c15 482 (type-break-cancel-schedule)
e7b20417 483 (type-break-time-warning-schedule time 'reset)
846f6dd9 484 (type-break-run-at-time (max 1 time) nil 'type-break-alarm)
defa7346
NF
485 (setq type-break-time-next-break
486 (type-break-time-sum (current-time) time)))
622aca7c 487
4cf64c15 488(defun type-break-cancel-schedule ()
e7b20417 489 (type-break-cancel-time-warning-schedule)
846f6dd9 490 (type-break-cancel-function-timers 'type-break-alarm)
be8d412c
NF
491 (setq type-break-alarm-p nil)
492 (setq type-break-time-next-break nil))
458401b6 493
e7b20417 494(defun type-break-time-warning-schedule (&optional time resetp)
b4dc9e6a 495 (let ((type-break-current-time-warning-interval nil))
e7b20417 496 (type-break-cancel-time-warning-schedule))
846f6dd9 497 (add-hook 'type-break-post-command-hook 'type-break-time-warning 'append)
e7b20417
NF
498 (cond
499 (type-break-time-warning-intervals
500 (and resetp
501 (setq type-break-current-time-warning-interval
502 type-break-time-warning-intervals))
503
504 (or time
505 (setq time (type-break-time-difference (current-time)
506 type-break-time-next-break)))
507
508 (while (and type-break-current-time-warning-interval
509 (> (car type-break-current-time-warning-interval) time))
510 (setq type-break-current-time-warning-interval
511 (cdr type-break-current-time-warning-interval)))
512
513 (cond
514 (type-break-current-time-warning-interval
515 (setq time (- time (car type-break-current-time-warning-interval)))
516 (setq type-break-current-time-warning-interval
517 (cdr type-break-current-time-warning-interval))
518
b4dc9e6a
NF
519 ;(let (type-break-current-time-warning-interval)
520 ; (type-break-cancel-time-warning-schedule))
846f6dd9 521 (type-break-run-at-time (max 1 time) nil 'type-break-time-warning-alarm)
b4dc9e6a
NF
522
523 (cond
524 (resetp
525 (setq type-break-warning-countdown-string nil))
526 (t
527 (setq type-break-warning-countdown-string (number-to-string time))
528 (setq type-break-warning-countdown-string-type "seconds"))))))))
e7b20417
NF
529
530(defun type-break-cancel-time-warning-schedule ()
846f6dd9 531 (type-break-cancel-function-timers 'type-break-time-warning-alarm)
e7b20417
NF
532 (remove-hook 'type-break-post-command-hook 'type-break-time-warning)
533 (setq type-break-current-time-warning-interval
b4dc9e6a
NF
534 type-break-time-warning-intervals)
535 (setq type-break-warning-countdown-string nil))
e7b20417 536
4cf64c15 537(defun type-break-alarm ()
846f6dd9
NF
538 (type-break-check-post-command-hook)
539 (setq type-break-alarm-p t)
540 (type-break-mode-line-countdown-or-break 'break))
458401b6 541
e7b20417 542(defun type-break-time-warning-alarm ()
846f6dd9 543 (type-break-check-post-command-hook)
e7b20417
NF
544 (type-break-time-warning-schedule)
545 (setq type-break-time-warning-count type-break-warning-repeat)
846f6dd9
NF
546 (type-break-time-warning)
547 (type-break-mode-line-countdown-or-break 'countdown))
e7b20417
NF
548
549\f
550(defun type-break-run-tb-post-command-hook ()
551 (and type-break-mode
552 (run-hooks 'type-break-post-command-hook)))
553
458401b6 554(defun type-break-check ()
4cf64c15
NF
555 "Ask to take a typing break if appropriate.
556This may be the case either because the scheduled time has come \(and the
557minimum keystroke threshold has been reached\) or because the maximum
558keystroke threshold has been exceeded."
e7b20417
NF
559 (let* ((min-threshold (car type-break-keystroke-threshold))
560 (max-threshold (cdr type-break-keystroke-threshold)))
561 (and type-break-good-rest-interval
562 (progn
563 (and (> (type-break-time-difference
564 type-break-time-last-command (current-time))
565 type-break-good-rest-interval)
566 (progn
567 (type-break-keystroke-reset)
846f6dd9 568 (type-break-mode-line-countdown-or-break nil)
e7b20417
NF
569 (setq type-break-time-last-break (current-time))
570 (type-break-schedule)))
571 (setq type-break-time-last-command (current-time))))
572
573 (and type-break-keystroke-threshold
b4dc9e6a
NF
574 (let ((keys (this-command-keys)))
575 (cond
576 ;; Ignore mouse motion
577 ((and (vectorp keys)
578 (consp (aref keys 0))
579 (memq (car (aref keys 0)) '(mouse-movement))))
580 (t
581 (setq type-break-keystroke-count
582 (+ type-break-keystroke-count (length keys)))))))
e7b20417 583
e7b20417
NF
584 (cond
585 (type-break-alarm-p
586 (cond
587 ((input-pending-p))
588 ((eq (selected-window) (minibuffer-window)))
589 ((and min-threshold
590 (< type-break-keystroke-count min-threshold))
591 (type-break-schedule))
592 (t
593 ;; If keystroke count is within min-threshold of
b4dc9e6a 594 ;; max-threshold, lower it to reduce the likelihood of an
e7b20417
NF
595 ;; immediate subsequent query.
596 (and max-threshold
597 min-threshold
598 (< (- max-threshold type-break-keystroke-count) min-threshold)
599 (progn
600 (type-break-keystroke-reset)
601 (setq type-break-keystroke-count min-threshold)))
602 (type-break-query))))
603 ((and type-break-keystroke-warning-intervals
604 max-threshold
605 (= type-break-keystroke-warning-count 0)
606 (type-break-check-keystroke-warning)))
607 ((and max-threshold
608 (> type-break-keystroke-count max-threshold)
609 (not (input-pending-p))
610 (not (eq (selected-window) (minibuffer-window))))
611 (type-break-keystroke-reset)
612 (setq type-break-keystroke-count (or min-threshold 0))
613 (type-break-query)))))
614
615;; This should return t if warnings were enabled, nil otherwise.
846f6dd9 616(defun type-break-check-keystroke-warning ()
b4dc9e6a
NF
617 ;; This is safe because the caller should have checked that the cdr was
618 ;; non-nil already.
e7b20417
NF
619 (let ((left (- (cdr type-break-keystroke-threshold)
620 type-break-keystroke-count)))
621 (cond
622 ((null (car type-break-current-keystroke-warning-interval))
623 nil)
624 ((> left (car type-break-current-keystroke-warning-interval))
625 nil)
626 (t
627 (while (and (car type-break-current-keystroke-warning-interval)
628 (< left (car type-break-current-keystroke-warning-interval)))
629 (setq type-break-current-keystroke-warning-interval
630 (cdr type-break-current-keystroke-warning-interval)))
631 (setq type-break-keystroke-warning-count type-break-warning-repeat)
632 (add-hook 'type-break-post-command-hook 'type-break-keystroke-warning)
b4dc9e6a
NF
633 (setq type-break-warning-countdown-string (number-to-string left))
634 (setq type-break-warning-countdown-string-type "keystrokes")
846f6dd9 635 (type-break-mode-line-countdown-or-break 'countdown)
e7b20417 636 t))))
4cf64c15 637
b4dc9e6a 638;; Arrange for a break query to be made, when the user stops typing furiously.
4cf64c15 639(defun type-break-query ()
b4dc9e6a
NF
640 (add-hook 'type-break-post-command-hook 'type-break-do-query))
641
b4dc9e6a
NF
642(defun type-break-do-query ()
643 (cond
846f6dd9
NF
644 ((not type-break-query-mode)
645 (type-break-noninteractive-query)
646 (type-break-schedule type-break-query-interval)
647 (remove-hook 'type-break-post-command-hook 'type-break-do-query))
648 ((sit-for 2)
b4dc9e6a
NF
649 (condition-case ()
650 (cond
651 ((let ((type-break-mode nil)
652 ;; yes-or-no-p sets this-command to exit-minibuffer,
653 ;; which hoses undo or yank-pop (if you happened to be
654 ;; yanking just when the query occurred).
655 (this-command this-command))
ad953485
NF
656 ;; Cancel schedule to prevent possibility of a second query
657 ;; from taking place before this one has even returned.
658 ;; The condition-case wrapper will reschedule on quit.
659 (type-break-cancel-schedule)
b4dc9e6a 660 (funcall type-break-query-function
ad953485
NF
661 (format "%s%s"
662 (type-break-time-stamp)
663 "Take a break from typing now? ")))
b4dc9e6a
NF
664 (type-break))
665 (t
666 (type-break-schedule type-break-query-interval)))
667 (quit
668 (type-break-schedule type-break-query-interval)))
669 (remove-hook 'type-break-post-command-hook 'type-break-do-query))))
458401b6 670
846f6dd9
NF
671(defun type-break-noninteractive-query (&optional ignored-args)
672 "Null query function which doesn't interrupt user and assumes `no'.
673It prints a reminder in the echo area to take a break, but doesn't enforce
674this or ask the user to start one right now."
675 (cond
676 (type-break-mode-line-message-mode)
677 (t
678 (beep t)
ad953485
NF
679 (message "%sYou should take a typing break now. Do `M-x type-break'."
680 (type-break-time-stamp))
846f6dd9
NF
681 (sit-for 1)
682 (beep t)
683 ;; return nil so query caller knows to reset reminder, as if user
684 ;; said "no" in response to yes-or-no-p.
685 nil)))
686
e7b20417
NF
687(defun type-break-time-warning ()
688 (cond
689 ((and (car type-break-keystroke-threshold)
690 (< type-break-keystroke-count (car type-break-keystroke-threshold))))
691 ((> type-break-time-warning-count 0)
b4dc9e6a
NF
692 (let ((timeleft (type-break-time-difference (current-time)
693 type-break-time-next-break)))
694 (setq type-break-warning-countdown-string (number-to-string timeleft))
695 (cond
696 ((eq (selected-window) (minibuffer-window)))
697 ;; Do nothing if the command was just a prefix arg, since that will
698 ;; immediately be followed by some other interactive command.
846f6dd9
NF
699 ;; Otherwise, it is particularly annoying for the sit-for below to
700 ;; delay redisplay when one types sequences like `C-u -1 C-l'.
b4dc9e6a 701 ((memq this-command '(digit-argument universal-argument)))
846f6dd9 702 ((not type-break-mode-line-message-mode)
b4dc9e6a
NF
703 ;; Pause for a moment so any previous message can be seen.
704 (sit-for 2)
ad953485
NF
705 (message "%sWarning: typing break due in %s."
706 (type-break-time-stamp)
b4dc9e6a
NF
707 (type-break-format-time timeleft))
708 (setq type-break-time-warning-count
709 (1- type-break-time-warning-count))))))
e7b20417 710 (t
b4dc9e6a
NF
711 (remove-hook 'type-break-post-command-hook 'type-break-time-warning)
712 (setq type-break-warning-countdown-string nil))))
e7b20417
NF
713
714(defun type-break-keystroke-warning ()
715 (cond
716 ((> type-break-keystroke-warning-count 0)
b4dc9e6a
NF
717 (setq type-break-warning-countdown-string
718 (number-to-string (- (cdr type-break-keystroke-threshold)
719 type-break-keystroke-count)))
e7b20417
NF
720 (cond
721 ((eq (selected-window) (minibuffer-window)))
846f6dd9
NF
722 ;; Do nothing if the command was just a prefix arg, since that will
723 ;; immediately be followed by some other interactive command.
724 ;; Otherwise, it is particularly annoying for the sit-for below to
725 ;; delay redisplay when one types sequences like `C-u -1 C-l'.
726 ((memq this-command '(digit-argument universal-argument)))
727 ((not type-break-mode-line-message-mode)
e7b20417 728 (sit-for 2)
ad953485
NF
729 (message "%sWarning: typing break due in %s keystrokes."
730 (type-break-time-stamp)
e7b20417
NF
731 (- (cdr type-break-keystroke-threshold)
732 type-break-keystroke-count))
733 (setq type-break-keystroke-warning-count
734 (1- type-break-keystroke-warning-count)))))
735 (t
736 (remove-hook 'type-break-post-command-hook
b4dc9e6a
NF
737 'type-break-keystroke-warning)
738 (setq type-break-warning-countdown-string nil))))
458401b6 739
846f6dd9
NF
740(defun type-break-mode-line-countdown-or-break (&optional type)
741 (cond
742 ((not type-break-mode-line-message-mode))
743 ((eq type 'countdown)
744 ;(setq type-break-mode-line-break-message-p nil)
745 (add-hook 'type-break-post-command-hook
746 'type-break-force-mode-line-update 'append))
747 ((eq type 'break)
748 ;; Alternate
749 (setq type-break-mode-line-break-message-p
750 (not type-break-mode-line-break-message-p))
751 (remove-hook 'type-break-post-command-hook
752 'type-break-force-mode-line-update))
753 (t
754 (setq type-break-mode-line-break-message-p nil)
755 (setq type-break-warning-countdown-string nil)
756 (remove-hook 'type-break-post-command-hook
757 'type-break-force-mode-line-update)))
758 (type-break-force-mode-line-update))
759
458401b6 760\f
be8d412c
NF
761;;;###autoload
762(defun type-break-statistics ()
763 "Print statistics about typing breaks in a temporary buffer.
764This includes the last time a typing break was taken, when the next one is
765scheduled, the keystroke thresholds and the current keystroke count, etc."
766 (interactive)
767 (with-output-to-temp-buffer "*Typing Break Statistics*"
768 (princ (format "Typing break statistics\n-----------------------\n
b4dc9e6a 769Typing break mode is currently %s.
846f6dd9
NF
770Interactive query for breaks is %s.
771Warnings of imminent typing breaks in mode line is %s.
b4dc9e6a
NF
772
773Last typing break ended : %s
be8d412c
NF
774Next scheduled typing break : %s\n
775Minimum keystroke threshold : %s
776Maximum keystroke threshold : %s
777Current keystroke count : %s"
b4dc9e6a 778 (if type-break-mode "enabled" "disabled")
846f6dd9
NF
779 (if type-break-query-mode "enabled" "disabled")
780 (if type-break-mode-line-message-mode "enabled" "disabled")
be8d412c
NF
781 (if type-break-time-last-break
782 (current-time-string type-break-time-last-break)
783 "never")
784 (if (and type-break-mode type-break-time-next-break)
6b62b567 785 (format "%s\t(%s from now)"
be8d412c 786 (current-time-string type-break-time-next-break)
e7b20417 787 (type-break-format-time
cc669dd8 788 (type-break-time-difference
e7b20417 789 (current-time)
cc669dd8 790 type-break-time-next-break)))
be8d412c
NF
791 "none scheduled")
792 (or (car type-break-keystroke-threshold) "none")
793 (or (cdr type-break-keystroke-threshold) "none")
794 type-break-keystroke-count))))
795
796;;;###autoload
b4dc9e6a 797(defun type-break-guesstimate-keystroke-threshold (wpm &optional wordlen frac)
be8d412c 798 "Guess values for the minimum/maximum keystroke threshold for typing breaks.
b4dc9e6a 799
be8d412c 800If called interactively, the user is prompted for their guess as to how
b4dc9e6a
NF
801many words per minute they usually type. This value should not be your
802maximum WPM, but your average. Of course, this is harder to gauge since it
803can vary considerably depending on what you are doing. For example, one
846f6dd9 804tends to type less when debugging a program as opposed to writing
b4dc9e6a
NF
805documentation. (Perhaps a separate program should be written to estimate
806average typing speed.)
807
808From that, this command sets the values in `type-break-keystroke-threshold'
809based on a fairly simple algorithm involving assumptions about the average
810length of words (5). For the minimum threshold, it uses about a fifth of
811the computed maximum threshold.
be8d412c
NF
812
813When called from lisp programs, the optional args WORDLEN and FRAC can be
814used to override the default assumption about average word length and the
815fraction of the maximum threshold to which to set the minimum threshold.
816FRAC should be the inverse of the fractional value; for example, a value of
8172 would mean to use one half, a value of 4 would mean to use one quarter, etc."
b4dc9e6a 818 (interactive "NOn average, how many words per minute do you type? ")
be8d412c 819 (let* ((upper (* wpm (or wordlen 5) (/ type-break-interval 60)))
f486195c 820 (lower (/ upper (or frac 5))))
be8d412c
NF
821 (or type-break-keystroke-threshold
822 (setq type-break-keystroke-threshold (cons nil nil)))
823 (setcar type-break-keystroke-threshold lower)
824 (setcdr type-break-keystroke-threshold upper)
825 (if (interactive-p)
826 (message "min threshold: %d\tmax threshold: %d" lower upper)
827 type-break-keystroke-threshold)))
828
829\f
e7b20417
NF
830;;; misc functions
831
832;; Compute the difference, in seconds, between a and b, two structures
833;; similar to those returned by `current-time'.
defa7346
NF
834;; Use addition rather than logand since that is more robust; the low 16
835;; bits of the seconds might have been incremented, making it more than 16
836;; bits wide.
846f6dd9 837(defun type-break-time-difference (a b)
e7b20417
NF
838 (+ (lsh (- (car b) (car a)) 16)
839 (- (car (cdr b)) (car (cdr a)))))
840
defa7346
NF
841;; Return (in a new list the same in structure to that returned by
842;; `current-time') the sum of the arguments. Each argument may be a time
843;; list or a single integer, a number of seconds.
844;; This function keeps the high and low 16 bits of the seconds properly
845;; balanced so that the lower value never exceeds 16 bits. Otherwise, when
846;; the result is passed to `current-time-string' it will toss some of the
846f6dd9 847;; "low" bits and format the time incorrectly.
defa7346
NF
848(defun type-break-time-sum (&rest tmlist)
849 (let ((high 0)
850 (low 0)
851 (micro 0)
852 tem)
853 (while tmlist
854 (setq tem (car tmlist))
855 (setq tmlist (cdr tmlist))
856 (cond
857 ((numberp tem)
858 (setq low (+ low tem)))
859 (t
860 (setq high (+ high (or (car tem) 0)))
861 (setq low (+ low (or (car (cdr tem)) 0)))
862 (setq micro (+ micro (or (car (cdr (cdr tem))) 0))))))
863
864 (and (>= micro 1000000)
865 (progn
866 (setq tem (/ micro 1000000))
867 (setq low (+ low tem))
868 (setq micro (- micro (* tem 1000000)))))
869
870 (setq tem (lsh low -16))
871 (and (> tem 0)
872 (progn
873 (setq low (logand low 65535))
874 (setq high (+ high tem))))
875
876 (list high low micro)))
877
ad953485
NF
878(defun type-break-time-stamp (&optional when)
879 (if (fboundp 'format-time-string)
880 (format-time-string type-break-time-stamp-format when)
881 ;; Emacs 19.28 and prior do not have format-time-string.
882 ;; In that case, result is not customizable. Upgrade today!
883 (format "[%s] " (substring (current-time-string when) 11 16))))
884
846f6dd9 885(defun type-break-format-time (secs)
e7b20417
NF
886 (let ((mins (/ secs 60)))
887 (cond
888 ((= mins 1) (format "%d minute" mins))
889 ((> mins 0) (format "%d minutes" mins))
890 ((= secs 1) (format "%d second" secs))
891 (t (format "%d seconds" secs)))))
892
893(defun type-break-keystroke-reset ()
894 (setq type-break-keystroke-count 0)
895 (setq type-break-keystroke-warning-count 0)
896 (setq type-break-current-keystroke-warning-interval
897 type-break-keystroke-warning-intervals)
898 (remove-hook 'type-break-post-command-hook 'type-break-keystroke-warning))
899
846f6dd9
NF
900(defun type-break-force-mode-line-update (&optional all)
901 "Force the mode-line of the current buffer to be redisplayed.
902With optional non-nil ALL, force redisplay of all mode-lines."
903 (and all (save-excursion (set-buffer (other-buffer))))
904 (set-buffer-modified-p (buffer-modified-p)))
905
906;; If an exception occurs in emacs while running the post command hook, the
907;; value of that hook is clobbered. This is because the value of the
908;; variable is temporarily set to nil while it's running to prevent
909;; recursive application, but it also means an exception aborts the routine
910;; of restoring it. This function is called from the timers to restore it,
911;; just in case.
912(defun type-break-check-post-command-hook ()
913 (add-hook 'post-command-hook 'type-break-run-tb-post-command-hook 'append))
914
915\f
916;;; Timer wrapper functions
917;;;
918;;; These shield type-break from variations in the interval timer packages
919;;; for different versions of emacs.
920
921(defun type-break-run-at-time (time repeat function)
6b61353c
KH
922 (condition-case nil (or (require 'timer) (require 'itimer)) (error nil))
923 (cond ((fboundp 'run-at-time)
924 (run-at-time time repeat function))
925 ((fboundp 'start-timer)
846f6dd9
NF
926 (let ((name (if (symbolp function)
927 (symbol-name function)
928 "type-break")))
6b61353c
KH
929 (start-timer name function time repeat)))
930 ((fboundp 'start-itimer)
846f6dd9
NF
931 (let ((name (if (symbolp function)
932 (symbol-name function)
933 "type-break")))
6b61353c 934 (start-itimer name function time repeat)))))
846f6dd9 935
6b61353c 936(defvar timer-dont-exit)
846f6dd9 937(defun type-break-cancel-function-timers (function)
6b61353c 938 (cond ((fboundp 'cancel-function-timers)
846f6dd9 939 (let ((timer-dont-exit t))
6b61353c
KH
940 (cancel-function-timers function)))
941 ((fboundp 'delete-timer)
846f6dd9
NF
942 (let ((list timer-list))
943 (while list
944 (and (eq (funcall 'timer-function (car list)) function)
6b61353c 945 (delete-timer (car list)))
846f6dd9 946 (setq list (cdr list)))))
6b61353c 947 ((fboundp 'delete-itimer)
2f3b1a8c
RS
948 (with-no-warnings
949 (let ((list itimer-list))
950 (while list
951 (and (eq (funcall 'itimer-function (car list)) function)
6b61353c 952 (delete-itimer (car list)))
2f3b1a8c 953 (setq list (cdr list))))))))
846f6dd9 954
e7b20417
NF
955\f
956;;; Demo wrappers
957
958;; This is a wrapper around hanoi that calls it with an arg large enough to
959;; make the largest discs possible that will fit in the window.
960;; Also, clean up the *Hanoi* buffer after we're done.
961(defun type-break-demo-hanoi ()
962 "Take a hanoiing typing break."
963 (and (get-buffer "*Hanoi*")
964 (kill-buffer "*Hanoi*"))
965 (condition-case ()
966 (progn
967 (hanoi (/ (window-width) 8))
968 ;; Wait for user to come back.
969 (read-char)
970 (kill-buffer "*Hanoi*"))
971 (quit
972 ;; eat char
973 (read-char)
974 (and (get-buffer "*Hanoi*")
975 (kill-buffer "*Hanoi*")))))
976
977;; This is a wrapper around life that calls it with a `sleep' arg to make
978;; it run a little more leisurely.
979;; Also, clean up the *Life* buffer after we're done.
980(defun type-break-demo-life ()
981 "Take a typing break and get a life."
982 (let ((continue t))
983 (while continue
984 (setq continue nil)
985 (and (get-buffer "*Life*")
986 (kill-buffer "*Life*"))
987 (condition-case ()
988 (progn
989 (life 3)
990 ;; wait for user to return
991 (read-char)
992 (kill-buffer "*Life*"))
993 (life-extinct
b4dc9e6a 994 (message "%s" (get 'life-extinct 'error-message))
e7b20417
NF
995 (sit-for 3)
996 ;; restart demo
997 (setq continue t))
998 (quit
999 (and (get-buffer "*Life*")
1000 (kill-buffer "*Life*")))))))
1001
defa7346 1002;; Boring demo, but doesn't use many cycles
e7b20417
NF
1003(defun type-break-demo-boring ()
1004 "Boring typing break demo."
defa7346 1005 (let ((rmsg "Press any key to resume from typing break")
e7b20417 1006 (buffer-name "*Typing Break Buffer*")
defa7346
NF
1007 line col pos
1008 elapsed timeleft tmsg)
e7b20417
NF
1009 (condition-case ()
1010 (progn
1011 (switch-to-buffer (get-buffer-create buffer-name))
1012 (buffer-disable-undo (current-buffer))
1013 (erase-buffer)
defa7346
NF
1014 (setq line (1+ (/ (window-height) 2)))
1015 (setq col (/ (- (window-width) (length rmsg)) 2))
e7b20417
NF
1016 (insert (make-string line ?\C-j)
1017 (make-string col ?\ )
defa7346
NF
1018 rmsg)
1019 (forward-line -1)
1020 (beginning-of-line)
1021 (setq pos (point))
1022 (while (not (input-pending-p))
1023 (delete-region pos (progn
1024 (goto-char pos)
1025 (end-of-line)
1026 (point)))
1027 (setq elapsed (type-break-time-difference
1028 type-break-time-last-break
1029 (current-time)))
1030 (cond
1031 (type-break-good-rest-interval
1032 (setq timeleft (- type-break-good-rest-interval elapsed))
1033 (if (> timeleft 0)
1034 (setq tmsg (format "You should rest for %s more"
1035 (type-break-format-time timeleft)))
1036 (setq tmsg (format "Typing break has lasted %s"
1037 (type-break-format-time elapsed)))))
1038 (t
1039 (setq tmsg (format "Typing break has lasted %s"
1040 (type-break-format-time elapsed)))))
1041 (setq col (/ (- (window-width) (length tmsg)) 2))
1042 (insert (make-string col ?\ ) tmsg)
1043 (goto-char (point-min))
1044 (sit-for 60))
e7b20417
NF
1045 (read-char)
1046 (kill-buffer buffer-name))
1047 (quit
1048 (and (get-buffer buffer-name)
1049 (kill-buffer buffer-name))))))
1050
1051\f
458401b6
NF
1052(provide 'type-break)
1053
104221a0
SE
1054(if type-break-mode
1055 (type-break-mode 1))
ad953485 1056
6b61353c 1057;;; arch-tag: 943a2eb3-07e6-420b-993f-96e4796f5fd0
458401b6 1058;;; type-break.el ends here