Tuesday, October 7, 2008

typing-speed-mode Emacs Minor Mode

I’ve been really into Lisp lately, especially Clojure, which looks really interesting. More on that some other time, though. Anyway, I’m always looking for an excuse to write code in Lisp – any Lisp.

As I was writing some prose in emacs this morning, I got into a real flow. I started to wonder how fast I was typing…and there was my excuse! About an hour later, I had written the code below. It’s an emacs minor mode that displays your typing speed (defined as the number of times self-insert-command has been run in the last five seconds, times twelve) in the mode-line. So you can watch your typing speed go up and down in real time. Note that you can customize a few things about it, like changing the default five-second window.

My emacs-fu (and my Lisp-fu) is not all that it could be, so the code is likely suboptimal in several ways, but it seems to work, and I always hate it when people say, “I’ll post the code once I get a chance to clean it up.” To hell with that: enjoy my code for the garbage it is :). 

;;; typing-speed.el --- Minor mode which displays your typing speed

;; Copyright (C) 2008 Wangdera Corporation

;; Permission is hereby granted, free of charge, to any person
;; obtaining a copy of this software and associated documentation
;; files (the "Software"), to deal in the Software without
;; restriction, including without limitation the rights to use,
;; copy, modify, merge, publish, distribute, sublicense, and/or sell
;; copies of the Software, and to permit persons to whom the
;; Software is furnished to do so, subject to the following
;; conditions:

;; The above copyright notice and this permission notice shall be
;; included in all copies or substantial portions of the Software.

;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
;; OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
;; OTHER DEALINGS IN THE SOFTWARE.

;; Author: Craig Andera <candera@wangdera.com>

;; Commentary: Invoke this minor mode to have your typing speed
;; continuously displayed in the mode line, in the format [75 WPM]
;; To use, just load this file and invoke (typing-speed-mode) or
;; (turn-on-typing-speed-mode)

(define-minor-mode typing-speed-mode
    "Displays your typing speed in the status bar."
  :lighter typing-speed-mode-text
  :group typing-speed
  (if typing-speed-mode
      (progn
    (add-hook 'post-command-hook 'typing-speed-post-command-hook)
    (setq typing-speed-event-queue '())
    (setq typing-speed-update-timer (run-with-timer 0 typing-speed-update-interval 'typing-speed-update)))
      (progn
    (remove-hook 'post-command-hook 'typing-speed-post-command-hook)
    (cancel-timer typing-speed-update-timer))))

(defcustom typing-speed-window 5
  "The window (in seconds) over which typing speed should be evaluated."
  :group 'typing-speed)

(defcustom typing-speed-mode-text-format " [%s WPM]"
  "A format string that controls how the typing speed is displayed in the mode line.
Must contain exactly one %s delimeter where the typing speed will be inserted."
  :group 'typing-speed)

(defcustom typing-speed-update-interval 1
  "How often the typing speed will update in the mode line, in seconds.
It will always also update after every command."
  :group 'typing-speed)

(defvar typing-speed-mode-text (format typing-speed-mode-text-format 0))
(defvar typing-speed-event-queue '())
(defvar typing-speed-update-timer nil)

(defun typing-speed-post-command-hook ()
  "When typing-speed-mode is enabled, fires after every command. If the
command is self-insert-command, log it as a keystroke and update the
typing speed."
  (if (eq this-command 'self-insert-command)
    (let ((current-time (float-time)))
      (push current-time typing-speed-event-queue)
      (typing-speed-update))))

(defun typing-speed-update ()
  "Calculate and display the typing speed."
  (let ((current-time (float-time)))
    (setq typing-speed-event-queue
      (typing-speed-remove-old-events
       (- current-time typing-speed-window)
       typing-speed-event-queue))
    (typing-speed-message-update)))

(defun typing-speed-message-update ()
  "Updates the status bar with the current typing speed"
  (let* ((chars-per-second (/ (length typing-speed-event-queue) (float typing-speed-window)))
     (chars-per-min (* chars-per-second 60))
     (words-per-min (/ chars-per-min 5)))
    (setq typing-speed-mode-text (format " [%s WPM]" (floor words-per-min)))
    (force-mode-line-update)))

(defun typing-speed-remove-old-events (threshold queue)
  "Removes events older than than the threshold (in seconds) from the specified queue"
  (if (or (null queue)
      (> threshold (car queue)))
      nil
      (cons (car queue)
        (typing-speed-remove-old-events threshold (cdr queue)))))

(defun turn-on-typing-speed ()
  "Turns on typing-speed-mode"
  (if (not typing-speed-mode)
      (typing-speed-mode)))

(defun turn-off-typing-speed ()
  "Turns off typing-speed-mode"
  (if typing-speed-mode
      (typing-speed-mode)))

7 comments:

  1. now is the time for all men to come to the aid of their country

    ReplyDelete
  2. And how fast do you type?

    ReplyDelete
  3. My speed floats somewhere in the 70 WPM range for prose. Bursts as high as 125 WPM. Keep in mind this is a five-second sliding window.

    ReplyDelete
  4. It’s the end of the year, and although I skipped 2007, it is my habit to take a look back here on matters

    ReplyDelete
  5. Hi Craig - I'm trying to run your program on emacs 21.4.1, and I'm running into some problems:

    Debugger entered--Lisp error: (void-variable typing speed)

    (custom-declare-variable (quote typing-speed-mode-hook) (quote nil) "Hook run at the end of function `typing-speed-mode'." :group typing-speed :type (quote hook))
    (defcustom typing-speed-mode-hook nil "Hook run at the end of function `typing-speed-mode'." :group typing-speed :type (quote hook))
    (progn (progn (defvar typing-speed-mode nil "Non-nil if Typing-Speed mode is enabled.\nUse the command `typing-speed-mode' to change this variable.") (make-variable-buffer-local ...)) (defun typing-speed-mode (&optional arg) "Displays your typing speed in the status bar." (interactive) (setq typing-speed-mode ...) (if typing-speed-mode ... ...) (run-hooks ... ...) (if ... ...) (force-mode-line-update) typing-speed-mode) :autoload-end (defcustom typing-speed-mode-hook nil "Hook run at the end of function `typing-speed-mode'." :group typing-speed :type (quote hook)) nil (add-minor-mode (quote typing-speed-mode) (quote typing-speed-mode-text) (if ... ...)) nil)
    (define-minor-mode typing-speed-mode "Displays your typing speed in the status bar." :lighter typing-speed-mode-text :group typing-speed (if typing-speed-mode (progn ... ... ...) (progn ... ...)))
    eval-buffer(# nil "/home/mprussel/summer/typespeed.el" nil t)
    load-with-code-conversion("/home/mprussel/summer/typespeed.el" "/home/mprussel/summer/typespeed.el" nil nil)
    load("/home/mprussel/summer/typespeed.el" nil nil t)
    load-file("typespeed.el")
    eval((load-file "typespeed.el"))
    eval-last-sexp-1(t)
    eval-last-sexp(t)
    eval-print-last-sexp()
    * call-interactively(eval-print-last-sexp)
    execute-extended-command(nil)
    * call-interactively(execute-extended-command)

    ReplyDelete
  6. Figured it out! It wouldn't load because there is no ' mark before "typing-speed" in the first instance of
    :group typingspeed

    ReplyDelete
  7. Why were so many do- it- yourself- sellers ineffective in arranging a market sale for their home? Because selling a home entails certain knowledge and skills that are acquired from practice over time in order to be effective. To demonstrate the point

    ReplyDelete