diff lisp/type-break.el @ 8276:73b85998c868

type-break-mode: Make variable `nil' by default. type-break-mode (function): If setting to t and mode was already enabled, don't reschedule breaks or reset keystroke counter. type-break-good-rest-interval: New variable. type-break-time-difference: New inline function (defsubst). tyype-break-time-last-break, type-break-time-next-break, type-break-time-last-command: New variables. type-break-check: Reset timers and counters if user has been idle more than type-break-good-rest-interval seconds (assuming it's set). Never set keystroke counter to be less than the min threshold. type-break: Admonish user if s/he rested less than type-break-good-rest-interval seconds (assuming it's set). type-break-demo-hanoi: Eat a char when quitting. type-break-statistics, type-break-guestimate-keystroke-threshold: New commands.
author Noah Friedman <friedman@splode.com>
date Mon, 18 Jul 1994 15:21:50 +0000
parents 4fdf77f4e45c
children e41f372e0ea3
line wrap: on
line diff
--- a/lisp/type-break.el	Mon Jul 18 07:37:18 1994 +0000
+++ b/lisp/type-break.el	Mon Jul 18 15:21:50 1994 +0000
@@ -1,18 +1,16 @@
-;;; type-break.el --- take breaks from typing at appropriate intervals
+;;; type-break.el --- encourage rests from typing at appropriate intervals
 
-;;; Copyright (C) 1994 Roland McGrath
 ;;; Copyright (C) 1994 Noah S. Friedman
 
 ;;; Author: Noah Friedman <friedman@prep.ai.mit.edu>
-;;;         Roland McGrath <roland@prep.ai.mit.edu>
 ;;; Maintainer: friedman@prep.ai.mit.edu
 ;;; Keywords: extensions, timers
-;;; Status: works in GNU Emacs 19
+;;; Status: known to work in GNU Emacs 19.25 or later.
 ;;; Created: 1994-07-13
 
 ;;; LCD Archive Entry:
 ;;; type-break|Noah Friedman|friedman@prep.ai.mit.edu|
-;;; take breaks from typing at appropriate intervals|
+;;; encourage rests from typing at appropriate intervals|
 ;;; $Date$|$Revision$||
 
 ;;; $Id$
@@ -33,13 +31,32 @@
 ;;; Inc.; 675 Massachusetts Avenue; Cambridge, MA 02139, USA.
 
 ;;; Commentary:
+
+;;; The docstring for the function `type-break-mode' summarizes most of the
+;;; details of the interface.
+
+;;; This package relies on the assumption that you live entirely in emacs,
+;;; as the author does.  If that's not the case for you (e.g. you often
+;;; suspend emacs or work in other windows) then this won't help very much;
+;;; it will depend on just how often you switch back to emacs.  At the very
+;;; least, you will want to turn off the keystroke thresholds and rest
+;;; interval tracking.
+
+;;; Setting type-break-good-rest-interval makes emacs cons like a maniac
+;;; because of repeated calls to `current-time'.  There's not really any
+;;; good way to avoid this without disabling the variable.
+
+;;; This package was inspired by Roland McGrath's hanoi-break.el.
+
 ;;; Code:
 
 
 (require 'timer)
 
+;; Make this nil initially so that the call to type-break-mode at the end
+;; will cause scheduling and so forth to happen.
 ;;;###autoload
-(defvar type-break-mode t
+(defvar type-break-mode nil
   "*Non-`nil' means typing break mode is enabled.
 See the docstring for the `type-break-mode' command for more information.")
 
@@ -48,6 +65,17 @@
   "*Number of seconds between scheduled typing breaks.")
 
 ;;;###autoload
+(defvar type-break-good-rest-interval (/ type-break-interval 6)
+  "*Number of seconds of idle time considered to be an adequate typing rest.
+
+When this variable is non-`nil', emacs checks the idle time between
+keystrokes.  If this idle time is long enough to be considered a "good"
+rest from typing, then the next typing break is simply rescheduled for later.
+
+The user will also be admonished if a forced break isn't at least as long
+as this time, to remind them to rest longer next time.")
+
+;;;###autoload
 (defvar type-break-query-interval 60
   "*Number of seconds between queries to take a break, if put off.
 The user will continue to be prompted at this interval until he or she
@@ -67,20 +95,23 @@
          (lower (/ upper 4)))
     (cons lower upper))
   "*Upper and lower bound on number of keystrokes for considering typing break.
+This structure is a pair of numbers.
 
-This structure is a pair of numbers.  The first number is the minimum
-number of keystrokes that must have been entered since the last typing
-break before considering another one, even if the scheduled time has
-elapsed; the break is simply rescheduled until later if the minimum
-threshold hasn't been reached.
+The first number is the minimum number of keystrokes that must have been
+entered since the last typing break before considering another one, even if
+the scheduled time has elapsed; the break is simply rescheduled until later
+if the minimum threshold hasn't been reached.  If this first value is nil,
+then there is no minimum threshold; as soon as the scheduled time has
+elapsed, the user will always be queried.
 
 The second number is the maximum number of keystrokes that can be entered
 before a typing break is requested immediately, pre-empting the originally
-scheduled break.
+scheduled break.  If this second value is nil, then no pre-emptive breaks
+will occur; only scheduled ones will.
 
 Keys with bucky bits (shift, control, meta, etc) are counted as only one
 keystroke even though they really require multiple keys to generate them.")
-  
+
 ;;;###autoload
 (defvar type-break-query-function 'yes-or-no-p
   "*Function to use for making query for a typing break.
@@ -98,13 +129,25 @@
 
 ;; These are internal variables.  Do not set them yourself.
 
-;; Number of commands (roughly # of keystrokes) recorded since last break.
-(defvar type-break-keystroke-count 0)
-
 ;; Non-nil when a scheduled typing break is due.
 (defvar type-break-alarm-p nil)
 
+(defvar type-break-keystroke-count 0)
+
+(defvar type-break-time-last-break nil)
+(defvar type-break-time-next-break nil)
+(defvar type-break-time-last-command (current-time))
+
 
+;; Compute the difference, in seconds, between a and b, two structures
+;; similar to those returned by `current-time'.
+;; Use addition rather than logand since I found it convenient to add
+;; seconds to the cdr of some of my stored time values, which may throw off
+;; the number of bits in the cdr.
+(defsubst type-break-time-difference (a b)
+  (+ (lsh (- (car b) (car a)) 16)
+     (- (car (cdr b)) (car (cdr a)))))
+
 ;;;###autoload
 (defun type-break-mode (&optional prefix)
   "Enable or disable typing-break mode.
@@ -126,10 +169,10 @@
 same name, though setting it in that way doesn't reschedule a break or
 reset the keystroke counter.
 
-When this function enables the mode, it schedules a break with
-`type-break-schedule' to make sure one occurs (the user can call that
-command to reschedule the break at any time).  It also initializes the
-keystroke counter.
+If the mode was previously disabled and is enabled as a consequence of
+calling this function, it schedules a break with `type-break-schedule' to
+make sure one occurs (the user can call that command to reschedule the
+break at any time).  It also initializes the keystroke counter.
 
 The variable `type-break-interval' specifies the number of seconds to
 schedule between regular typing breaks.  This variable doesn't directly
@@ -140,35 +183,47 @@
 schedule between repeated queries for breaks when the user answers \"no\"
 to the previous query.
 
-The variable `type-break-keystroke-theshold' is used to determine the
-thresholds at which typing breaks should be considered.
+The variable `type-break-good-rest-interval' specifies the minimum amount
+of time which is considered a reasonable typing break.  Whenever that time
+has elapsed, typing breaks are automatically rescheduled for later even if
+emacs didn't prompt you to take one first.  You can disable this behavior.
+
+The variable `type-break-keystroke-threshold' is used to determine the
+thresholds at which typing breaks should be considered.  You can use
+the command `type-break-guestimate-keystroke-threshold' to try to
+approximate good values for this.
 
 The variable `type-break-query-function' should contain a function (or the
 symbolic name of a function) to be used to query the user for typing
-breaks."
+breaks.
+
+Finally, the command `type-break-statistics' prints interesting things."
   (interactive "P")
   ;; make sure it's there.
   (add-hook 'post-command-hook 'type-break-check 'append)
 
-  (cond
-   ((null prefix)
-    (setq type-break-mode (not type-break-mode)))
-   ((numberp (prefix-numeric-value prefix))
-    (setq type-break-mode (>= (prefix-numeric-value prefix) 0)))
-   (prefix
-    (setq type-break-mode t))
-   (t
-    (setq type-break-mode nil)))
+  (let ((already-enabled type-break-mode))
+    (cond
+     ((null prefix)
+      (setq type-break-mode (not type-break-mode)))
+     ((numberp (prefix-numeric-value prefix))
+      (setq type-break-mode (>= (prefix-numeric-value prefix) 0)))
+     (prefix
+      (setq type-break-mode t))
+     (t
+      (setq type-break-mode nil)))
 
-  (cond
-   (type-break-mode
-    (setq type-break-keystroke-count 0)
-    (type-break-schedule)
-    (and (interactive-p)
-         (message "type-break-mode is enabled and reset")))
-   ((interactive-p)
-    (message "type-break-mode is disabled")))
-
+    (cond
+     ((and already-enabled type-break-mode)
+      (and (interactive-p)
+           (message "type-break-mode was already enabled")))
+     (type-break-mode
+      (setq type-break-keystroke-count 0)
+      (type-break-schedule)
+      (and (interactive-p)
+           (message "type-break-mode is enabled and reset")))
+     ((interactive-p)
+      (message "type-break-mode is disabled"))))
   type-break-mode)
 
 ;;;###autoload
@@ -182,13 +237,14 @@
 as per the function `type-break-schedule', and the keystroke counter is
 reset."
   (interactive)
+  (setq type-break-time-last-break (current-time))
   (save-window-excursion
     ;; Eat the screen.
     (and (eq (selected-window) (minibuffer-window))
          (other-window 1))
     (delete-other-windows)
     (scroll-right (window-width))
-    (message "Press any key to finish typing break.")
+    (message "Press any key to resume from typing break.")
 
     (random t)
     (let* ((len (length type-break-demo-function-vector))
@@ -196,10 +252,16 @@
            (fn (aref type-break-demo-function-vector idx)))
       (condition-case ()
           (funcall fn)
-        (error nil)))
+        (error nil))))
 
-    (setq type-break-keystroke-count 0)
-    (type-break-schedule)))
+  (and type-break-good-rest-interval
+       (< (type-break-time-difference type-break-time-last-command
+                                      (current-time))
+          type-break-good-rest-interval)
+       (message "That typing break wasn't really long enough.  Rest more next time."))
+
+  (setq type-break-keystroke-count 0)
+  (type-break-schedule))
 
 
 ;;;###autoload
@@ -211,7 +273,11 @@
   (or time (setq time type-break-interval))
   ;; Remove any old scheduled break
   (type-break-cancel-schedule)
-  (run-at-time time nil 'type-break-alarm))
+  (run-at-time time nil 'type-break-alarm)
+
+  (setq type-break-time-next-break (current-time))
+  (setcar (cdr type-break-time-next-break)
+          (+ time (car (cdr type-break-time-next-break)))))
 
 (defun type-break-cancel-schedule ()
   "Cancel scheduled typing breaks.
@@ -221,7 +287,8 @@
   (interactive)
   (let ((timer-dont-exit t))
     (cancel-function-timers 'type-break-alarm))
-  (setq type-break-alarm-p nil))
+  (setq type-break-alarm-p nil)
+  (setq type-break-time-next-break nil))
 
 (defun type-break-alarm ()
   "This function is run when a scheduled typing break is due."
@@ -234,27 +301,56 @@
 keystroke threshold has been exceeded."
   (cond
    (type-break-mode
-    (let* ((pair (and (consp type-break-keystroke-threshold)
-                      type-break-keystroke-threshold))
-           (min-threshold (car pair))
-           (max-threshold (cdr pair)))
-      (and pair
-           (stringp (this-command-keys))
+    (let* ((threshold-pair (and (consp type-break-keystroke-threshold)
+                                type-break-keystroke-threshold))
+           (min-threshold (car threshold-pair))
+           (max-threshold (cdr threshold-pair)))
+
+      ;; Reset schedule and keystroke count if user has been idle longer
+      ;; than a normal resting period.
+      (cond
+       (type-break-good-rest-interval
+         (and (> (type-break-time-difference type-break-time-last-command
+                                            (current-time))
+                type-break-good-rest-interval)
+             (progn
+               (setq type-break-keystroke-count 0)
+               (type-break-schedule)))
+        (setq type-break-time-last-command (current-time))))
+
+      (and threshold-pair
            (setq type-break-keystroke-count
                  (+ type-break-keystroke-count (length (this-command-keys)))))
+
       (cond
        ((input-pending-p))
-       ((and (numberp max-threshold)
-             (> type-break-keystroke-count max-threshold))
-        (setq type-break-keystroke-count 0)
-        (type-break-query))
        (type-break-alarm-p
         (cond
-         ((and (numberp min-threshold)
+         ((and min-threshold
                (< type-break-keystroke-count min-threshold)))
          (t
-          (setq type-break-keystroke-count 0)
-          (type-break-query)))))))))
+          ;; If the keystroke count is within min-threshold characters of
+          ;; the maximum threshold, set the count to min-threshold.  That
+          ;; way, if the count was really close the threshold and the user
+          ;; doesn't choose to take a break now, s/he won't be pestered
+          ;; almost immediately after saying "no"; that's what the query
+          ;; interval delay is for.
+          ;; On the other hand, don't set it too small (make it at least
+          ;; min-threshold); that way we can be sure the user will be asked
+          ;; again to take a break after the query interval has elapsed.
+          ;; If the user chooses to take a break now, the break function
+          ;; will reset the keystroke count anyway.
+          (and max-threshold
+               min-threshold
+               (> (- max-threshold type-break-keystroke-count) min-threshold)
+               (setq type-break-keystroke-count min-threshold))
+          (type-break-query))))
+       ((and max-threshold
+             (> type-break-keystroke-count max-threshold))
+        ;; Set it to the min threshold if possible, to be sure the user
+        ;; will be pestered again in at least a minute.
+        (setq type-break-keystroke-count (or min-threshold 0))
+        (type-break-query)))))))
 
 (defun type-break-query ()
   (condition-case ()
@@ -280,7 +376,9 @@
         ;; Wait for user to come back.
         (read-char)
         (kill-buffer "*Hanoi*"))
-    (quit 
+    (quit
+     ;; eat char
+     (read-char)
      (and (get-buffer "*Hanoi*")
           (kill-buffer "*Hanoi*")))))
 
@@ -297,11 +395,65 @@
         ;; Wait for user to come back.
         (read-char)
         (kill-buffer "*Life*"))
-    (quit 
+    (quit
      (and (get-buffer "*Life*")
           (kill-buffer "*Life*")))))
 
 
+;;;###autoload
+(defun type-break-statistics ()
+  "Print statistics about typing breaks in a temporary buffer.
+This includes the last time a typing break was taken, when the next one is
+scheduled, the keystroke thresholds and the current keystroke count, etc."
+  (interactive)
+  (with-output-to-temp-buffer "*Typing Break Statistics*"
+    (princ (format "Typing break statistics\n-----------------------\n
+Last typing break           : %s
+Next scheduled typing break : %s\n
+Minimum keystroke threshold : %s
+Maximum keystroke threshold : %s
+Current keystroke count     : %s"
+                   (if type-break-time-last-break
+                       (current-time-string type-break-time-last-break)
+                     "never")
+                   (if (and type-break-mode type-break-time-next-break)
+                       (format "%s\t(%d minutes from now)"
+                               (current-time-string type-break-time-next-break)
+                               (/ (type-break-time-difference
+                                   (current-time) type-break-time-next-break)
+                                  60))
+                     "none scheduled")
+                   (or (car type-break-keystroke-threshold) "none")
+                   (or (cdr type-break-keystroke-threshold) "none")
+                   type-break-keystroke-count))))
+
+;;;###autoload
+(defun type-break-guestimate-keystroke-threshold (wpm &optional wordlen frac)
+  "Guess values for the minimum/maximum keystroke threshold for typing breaks.
+If called interactively, the user is prompted for their guess as to how
+many words per minute they usually type.  From that, the command sets the
+values in `type-break-keystroke-threshold' based on a fairly simple
+algorithm involving assumptions about the average length of words (5).
+For the minimum threshold, it uses about a quarter of the computed maximum
+threshold.
+
+When called from lisp programs, the optional args WORDLEN and FRAC can be
+used to override the default assumption about average word length and the
+fraction of the maximum threshold to which to set the minimum threshold.
+FRAC should be the inverse of the fractional value; for example, a value of
+2 would mean to use one half, a value of 4 would mean to use one quarter, etc."
+  (interactive "nHow many words per minute do you type? ")
+  (let* ((upper (* wpm (or wordlen 5) (/ type-break-interval 60)))
+         (lower (/ upper (or frac 4))))
+    (or type-break-keystroke-threshold
+        (setq type-break-keystroke-threshold (cons nil nil)))
+    (setcar type-break-keystroke-threshold lower)
+    (setcdr type-break-keystroke-threshold upper)
+    (if (interactive-p)
+        (message "min threshold: %d\tmax threshold: %d" lower upper)
+      type-break-keystroke-threshold)))
+
+
 (provide 'type-break)
 
 (type-break-mode t)