Merge pull request #5 from ian-kelling/master
authorzk_phi <zk-phi@users.noreply.github.com>
Fri, 4 Jul 2014 02:41:44 +0000 (11:41 +0900)
committerzk_phi <zk-phi@users.noreply.github.com>
Fri, 4 Jul 2014 02:41:44 +0000 (11:41 +0900)
Merge pull request #5 from ian-kelling/master

Readme.org
screencast.gif
spray.el

index 7e7f5198fd6c1493c389c0d89906616899321507..9485f1b8a0fc0b75528176e12f0b3652a8e17f58 100644 (file)
@@ -2,14 +2,56 @@
 
 A speed reading mode for Emacs.
 
-The algorithm is taken from [[https://github.com/Miserlou/OpenSpritz][OpenSpritz]] (with a few modifications).
+The algorithm is inspired from [[https://github.com/Miserlou/OpenSpritz][OpenSpritz]].
 
-NOTE: This is a WIP project and thus APIs may change.
 
 ** Screencast
 
 [[screencast.gif]]
 
+The story continues at [[https://www.gnu.org/philosophy/right-to-read.html][gnu.org]]
+
+
+** Deficiencies in similar projects which spray.el solves:
+
+Mostly as of June 28, 2014.
+
+For all other projects:
+- It is very difficult to save your place and come back later. Emacs has lots of built in features like bookmarks or just making a note which make this easy.
+- No keybinds. This is especially useful for common, quickly used functionality like pause and back up.
+
+Specific projects:
+- https://github.com/jbmartinez/speed-readerff firefox extension.
+- https://github.com/cameron/squirt javascript bookmarklet.
+- https://github.com/Miserlou/OpenSpritz javascript bookmarklet.
+  - Very difficult selecting where to start / what exactly to read. 
+  - Very difficult to figure out where you are within a document. 
+
+
+- https://github.com/chaimpeck/spray website you paste text into. 
+  - Very difficult to figure out where you are within a document. 
+
+- https://github.com/xypiie/spread0r Perl program.  
+  - No apparent way to resize text.
+  - Very difficult to figure out where you are within a document. 
+
+
+- https://github.com/the-happy-hippo/sprits-it website or bookmarklet. Looks most promising of the web based projects.
+  - Very difficult selecting where to start / what exactly to read. 
+  - Somewhat difficult to figure out where you are in a document.
+
+
+Programs that I, Ian Kelling, didn't try, and why. They also probably have a lot of the problems listed above.
+- https://github.com/pasky/speedread No simple way to resize text from default of too small.
+- https://github.com/ds300/jetzt Chrome plugin: Chrome is proprietary, and its open source version is unfriendly to linux distros so they don't package it, and it's readme does not list any benefit to this program over others.
+- https://github.com/Fr4ncis/openspritz-ios ios program. I don't have an ios device, and this does not appear to have any compelling features.
+- https://github.com/OnlyInAmerica/OpenSpritz-Android android program. If I knew how to quickly move reading material to my phone, I would try this out. But, it's readme shows that the program is pretty sparse and would have some of the same problems as listed above, so not worth it.
+
+
+Downsides compared to similar projects:
+- The emacs learning curve.
+- Would be hard to get running on a phone.
+
 ** Installation
 
 Put spray.el into a "load-path"ed directory, and load it in your init
@@ -35,7 +77,15 @@ In spray-mode buffers, following commands are available.
 - =spray-forward-word= (l, <right>) ::
      inverse of =spray-backward-word=
 
-Press any key else to quit =spray-mode=.
+- =spray-faster= (f) ::
+     increases speed
+
+- =spray-slower= (s) ::
+     decreases speed
+
+- =spray-quit= (q, <return>) ::
+     quit =spray-mode=
+
 
 ** Customization
 
@@ -43,6 +93,40 @@ You may customize spray by modifying following items:
 
 - [Variable] spray-wpm
 - [Variable] spray-height
+- [Variable] spray-margin-top
+- [Variable] spray-margin-left
+- [Variable] spray-ramp
 - [Keymap] spray-mode-map
 - [Face] spray-base-face
-- [Face] spray-orp-face
+- [Face] spray-accent-face
+
+** Algorithm details
+
+*** Main algorithm code location
+In =./spray.el=, the functions =spray--word-at-point=, =spray--update= and =spray-start= contain the main algorithm of choosing a word, an accent character and an interval to display it. Word splitting is also duplicated the same way as in those functions throughout ./spray.el.
+
+*** Algorithm translated from code to english
+
+Words are split at space, tab, newline, and emdash characters. If any of the characters =.!?;= appear at the end of the word, a blank word is appended to the current word. 
+
+Each word is displayed for (60 / the choosen wpm) seconds, except if the word ends with ,:— or is greater than 9 characters long, in which case it is displayed for twice as long.
+
+When started, an added delay is optionally added, based on the spray-ramp variable. See it's documentation for details.
+
+The accent location is chosen as the nth character in a word, depending on its length, based on the following table
+| length | accent position |
+|      1 |               1 |
+|    2-5 |               2 |
+|    6-9 |               3 |
+|  10-13 |               4 |
+|    14+ |               5 |
+
+*** Why?
+Based on a quick count, similar programs are implemented in 6 languages, 9 platforms and 11+ projects. Every one has a similar algorithm to choose a word, an accent character and an interval to display it. How to do that best is quite important. However, how each project does it is generally buried in unrelated code, and mostly undocumented. It is prohibitively time consuming to figure out the algorithm and differences between them for each project. This sucks. Users would like to know so they can pick one. Developers would like to know so they can get ideas and spread improvements among different projects.
+
+So, I am opening a bug on every project I find, asking them to document what file and function(s) their algorithm is implemented in, and preferably to document it in english as well. This section is an example.
+
+
+** Contributions are welcome!
+
+There's lots of features and enhancements to do. The algorithm and interface could certainly be improved in various ways.
index dafc6ad38e66e302cfb925e5ddd745aac2af9214..0dbd23f2fbfa85728de67e5a4d544ff88650e920 100644 (file)
Binary files a/screencast.gif and b/screencast.gif differ
index aab9f8e1988a0e9ada1e137e70f5542ef6a97106..977fc6f9abbd8c476d65ce3d91c5d20497f91683 100644 (file)
--- a/spray.el
+++ b/spray.el
@@ -1,4 +1,4 @@
-;;; cedit.el --- a speed reading mode
+;;; spray.el --- a speed reading mode
 
 ;; Copyright (C) 2014 zk_phi
 
@@ -18,7 +18,8 @@
 
 ;; Author: zk_phi
 ;; URL: http://hins11.yu-yake.com/
-;; Version: 0.0.1
+;; Author: Ian Kelling <ian@iankelling.org>
+;; Version: 0.0.2
 
 ;;; Commentary:
 
 ;;
 ;; For more informations, see Readme.org.
 
+;; Known bugs.
+;; repeated words are indistinguishable, for example
+;; "going, going, gone" reads like going, gone, with a slight delay.
+;;
+;; sentences (like this) should trigger a pause for ( and )
+
 ;;; Change Log:
 ;; 0.0.0 test release
 ;; 0.0.1 add spray-set-margins
+;; 0.0.2 margin options, speed control, better quit
 
 ;;; Code:
 
 
 (defvar spray-wpm 400 "words/min")
 (defvar spray-height 400 "height of characters")
+(defvar spray-margin-top 1 "character margin at top of buffer. Characters are as big as spray text characters.")
+(defvar spray-margin-left 1 "character margin at left of buffer. Characters are as big as spray text characters.")
+(defvar spray-ramp 2
+  "Ramp up to full speed. Pause for this multiple of wpm on the first word,
+decreasing by one for each subsequent word.")
 
 (defvar spray-mode-map
   (let ((km (make-sparse-keymap)))
     (define-key km (kbd "l") 'spray-forward-word)
     (define-key km (kbd "<left>") 'spray-backward-word)
     (define-key km (kbd "<right>") 'spray-forward-word)
+    (define-key km (kbd "f") 'spray-faster)
+    (define-key km (kbd "s") 'spray-slower)
+    (define-key km (kbd "q") 'spray-quit)
+    (define-key km (kbd "<return>") 'spray-quit)
     km)
   "keymap for spray-mode buffers")
 
 (make-face 'spray-base-face)
 (set-face-attribute 'spray-base-face nil
                     :background (face-background 'default)
-                    :foreground (face-foreground 'default))
+                    :foreground (face-foreground 'default)
+                    :slant 'normal)
 
-(make-face 'spray-orp-face)
-(set-face-attribute 'spray-orp-face nil
+(make-face 'spray-accent-face)
+(set-face-attribute 'spray-accent-face nil
                     :foreground "red"
                     :overline (face-foreground 'default)
-                    :underline (face-foreground 'default))
+                    :underline (face-foreground 'default)
+                    :slant 'normal)
 
 ;; * internal vars
 
-(defvar spray--margin-string "")
+(defvar spray--margin-string ""
+  "Currently not used.")
 (defvar spray--base-overlay nil)
-(defvar spray--orp-overlay nil)
+(defvar spray--accent-overlay nil)
 (defvar spray--running nil)
+(defvar spray--first-words 0)
+(defvar spray--initial-delay 0)
 (defvar spray--delay 0)
 (defvar spray--saved-cursor-type nil)
 (defvar spray--saved-buffer-face nil)
 (defvar spray--saved-restriction nil)
+(defvar spray--saved-smartparens-enabled nil)
 
 ;; * utility functions
 
-(defun spray-set-margins (left above)
-  "add margins before/above the spray text. each arguments can be
-an integer or a float value."
+(defun spray-set-margins ()
+  "Setup spray--margin-string"
   (setq spray--margin-string
-        (propertize " " 'display `((space-width ,left) (height ,(1+ above))))))
+        (concat (make-string spray-margin-top 10) ;; 10 = ascii newline
+                (make-string spray-margin-left 32)))) ;; 32 = ascii space
 
 ;; * the mode
 
@@ -98,23 +121,32 @@ an integer or a float value."
   :keymap spray-mode-map
   (cond (spray-mode
          (setq spray--base-overlay (make-overlay (point-min) (point-max))
-               spray--orp-overlay (make-overlay 0 0)
+               spray--accent-overlay (make-overlay 0 0)
                spray--saved-cursor-type cursor-type
                spray--saved-restriction (and (buffer-narrowed-p)
                                              (cons (point-min) (point-max)))
                spray--saved-buffer-face (and (boundp 'buffer-face-mode)
                                              buffer-face-mode
-                                             buffer-face-mode-face))
+                                             buffer-face-mode-face)
+               spray--saved-smartparens-enabled (and (boundp 'smartparens-mode)
+                                                     smartparens-mode)
+               spray--saved-highlight-symbol-enabled (and (boundp 'highlight-symbol-mode)
+                                                     highlight-symbol-mode))
+         ;; smartparens wrapping of all letter binds can cause problems.
+         ;; for example, it can cause auto-complete to activate
+         (and spray--saved-smartparens-enabled (smartparens-mode -1))
+         (and spray--saved-highlight-symbol-enabled (highlight-symbol-mode -1))
          (setq cursor-type nil)
          (let ((buffer-face-mode-face `(:height ,spray-height)))
            (buffer-face-mode 1))
          (overlay-put spray--base-overlay 'priority 100)
          (overlay-put spray--base-overlay 'face 'spray-base-face)
-         (overlay-put spray--orp-overlay 'priority 101)
-         (overlay-put spray--orp-overlay 'face 'spray-orp-face)
-         (add-hook 'pre-command-hook 'spray--pre-command-handler)
-         (spray-start/stop 1))
+         (overlay-put spray--accent-overlay 'priority 101)
+         (overlay-put spray--accent-overlay 'face 'spray-accent-face)
+         (spray-start))
         (t
+         (and spray--saved-smartparens-enabled (smartparens-mode 1))
+         (and spray--saved-highlight-symbol-enabled (highlight-symbol-mode 1))
          (setq cursor-type spray--saved-cursor-type)
          (if spray--saved-restriction
              (narrow-to-region (car spray--saved-restriction)
@@ -125,40 +157,48 @@ an integer or a float value."
              (let ((buffer-face-mode-face spray--saved-buffer-face))
                (buffer-face-mode 1)))
          (delete-overlay spray--base-overlay)
-         (delete-overlay spray--orp-overlay)
-         (remove-hook 'pre-command-hook 'spray--pre-command-handler)
-         (spray-start/stop -1))))
+         (delete-overlay spray--accent-overlay)
+         (spray-stop))))
 
-(defun spray--pre-command-handler ()
-  (unless (string-match "^spray-" (symbol-name this-command))
-    (spray-mode -1)))
+(defun spray-quit ()
+  "Exit spray mode."
+  (interactive)
+  (spray-mode -1))
 
 (defun spray--word-at-point ()
-  (skip-chars-backward "^\s\t\n")
+  (skip-chars-backward "^\s\t\n")
   (let* ((beg (point))
-         (len (skip-chars-forward "^\s\t\n"))
+         (len (+ (skip-chars-forward "^\s\t\n—") (skip-chars-forward "—")))
          (end (point))
-         (orp (+ beg (cl-case len
+         (accent (+ beg (cl-case len
                        ((1) 1)
                        ((2 3 4 5) 2)
                        ((6 7 8 9) 3)
                        ((10 11 12 13) 4)
                        (t 5)))))
+    ;; this fairly obfuscated, using magic numbers to store state
+    ;; it would be nice to sometime patch this so it is more readable.
+    ;; for greater than 9 length, we display for twice as long
+    ;; for some punctuation, we display a blank
     (setq spray--delay (+ (if (> len 9) 1 0)
                           (if (looking-at "\n[\s\t\n]") 3 0)
                           (cl-case (char-before)
                             ((?. ?! ?\? ?\;) 3)
-                            ((?, ?:) 1)
+                            ((?, ?: ?—) 1)
                             (t 0))))
-    (move-overlay spray--orp-overlay (1- orp) orp)
+    (move-overlay spray--accent-overlay (1- accent) accent)
     (move-overlay spray--base-overlay beg end)
+    (spray-set-margins)
     (overlay-put spray--base-overlay 'before-string
                  (concat spray--margin-string
-                         (make-string (- 5 (- orp beg)) ?\s)))
+                         (make-string (- 5 (- accent beg)) ?\s)))
     (narrow-to-region beg end)))
 
+
 (defun spray--update ()
-  (cond ((not (zerop spray--delay))
+  (cond ((not (zerop spray--initial-delay))
+         (setq spray--initial-delay (1- spray--initial-delay)))
+        ((not (zerop spray--delay))
          (setq spray--delay (1- spray--delay))
          (when (= spray--delay 2)
            (narrow-to-region (point) (point))))
@@ -166,38 +206,77 @@ an integer or a float value."
          (widen)
          (if (eobp)
              (spray-mode -1)
-           (skip-chars-forward "\s\t\n")
+           (when (not (zerop spray--first-words))
+             (setq spray--initial-delay spray--first-words)
+             (setq spray--first-words (1- spray--first-words)))
+           (skip-chars-forward "\s\t\n—")
            (spray--word-at-point)))))
 
 ;; * interactive commands
 
-(defun spray-start/stop (&optional switch)
+(defun spray-start/stop ()
+  "Toggle pause/unpause spray."
   (interactive)
-  (cond ((and (memql switch '(nil 1))
-              (not spray--running))
-         (setq spray--running
-               (run-with-timer 0 (/ 60.0 spray-wpm) 'spray--update)))
-        ((memql switch '(nil -1))
-         (cancel-timer spray--running)
-         (setq spray--running nil))
-        (t
-         nil)))
+  (or (spray-stop) (spray-start)))
+
+(defun spray-stop ()
+  "Pause spray.
+Returns t if spray was unpaused."
+  (interactive)
+  (prog1 spray--running
+    (when spray--running
+      (cancel-timer spray--running)
+      (setq spray--running nil))))
+
+(defun spray-start ()
+  "Start / resume spray."
+  (interactive)
+  (setq spray--first-words spray-ramp)
+  (setq spray--running
+        (run-with-timer 0 (/ 60.0 spray-wpm) 'spray--update)))
+
 
 (defun spray-forward-word ()
   (interactive)
-  (when spray--running (spray-start/stop -1))
+  (spray-stop)
   (widen)
-  (skip-chars-forward "\s\t\n")
+  (skip-chars-forward "\s\t\n")
   (spray--word-at-point))
 
 (defun spray-backward-word ()
   (interactive)
-  (when spray--running (spray-start/stop -1))
+  (spray-stop)
   (widen)
-  (skip-chars-backward "^\s\t\n")
-  (skip-chars-backward "\s\t\n")
+  (skip-chars-backward "^\s\t\n")
+  (skip-chars-backward "\s\t\n")
   (spray--word-at-point))
 
+(defun spray-faster ()
+  "Increases speed.
+
+Increases the wpm (words per minute) parameter. See the variable
+`spray-wmp'."
+  (interactive)
+  (spray-inc-wpm 20))
+
+(defun spray-slower ()
+  "Decreases speed.
+
+Decreases the wpm (words per minute) parameter. See the variable
+`spray-wmp'."
+  (interactive)
+  (spray-inc-wpm -20))
+
+(defun spray-inc-wpm (delta)
+  (let ((was-running spray--running))
+    (spray-stop)
+    (when (< 10 (+ spray-wpm delta))
+      (setq spray-wpm (+ spray-wpm delta)))
+    (and was-running (spray-backward-word))
+    (message "spray wpm: %d" spray-wpm)
+    (when was-running
+      (spray-start))))
+
 ;; * provide
 
 (provide 'spray)