;;; flyspell-timer.el --- Check Spelling in the idle cycle
;; This file is not part of Emacs
;; Author: Phillip Lord
;; Maintainer: Phillip Lord
;; Maintainer (XEmacs): Martin Kuehl (martin.kuehl@gmail.com)
;; Website: http://www.russet.org.uk
;; COPYRIGHT NOTICE
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;;
;; This file enables flyspell checking in the idle cycle. Add
;;
;; (require 'flyspell-timer)
;; (add-hook 'flyspell-mode-hook 'flyspell-timer-ensure-idle-timer)
;;
;; to your .emacs. `flyspell-timer-mode' is also checked, so set this
;; to nil buffer-local if you want to disable this in some
;; buffers. Other configuration are `flyspell-timer-chunk-size' and
;; `flyspell-timer-delay'. The system checks the former number of
;; words in each direction and then delays for the later number of
;; seconds. These are set conservatively, as flyspell can be fairly
;; intensive. Only displayed words (not the whole buffer) are checked
;; for much the same reason. Finally
;; `flyspell-timer-idle-timer-verbose' switches of the error
;; messages. These default to on, again, because this mode is CPU
;; intensive.
;;; History:
;;
;;; Bugs:
;;
;; I still don't understand idle timers. Other packages with idle
;; timers may well interfere.
;;; Code:
(defvar flyspell-timer-mode t)
(defvar flyspell-timer-idle-timer-verbose t)
;; (setq flyspell-timer-debug t)
(defvar flyspell-timer-debug nil)
(defvar flyspell-timer-chunk-size 1
"Size of chunks checked in idle cycle.")
(defvar flyspell-timer-delay 1
"Length of time delay between chunks.")
(defun flyspell-timer-ensure-idle-timer()
(interactive)
(unless nil
(if (not flyspell-timer-long-idle-timer)
(flyspell-timer-start-idle-timer))))
;; (setq flyspell-timer-long-idle-timer nil)
(defun flyspell-timer-start-idle-timer()
(setq flyspell-timer-long-idle-timer
(run-with-idle-timer 6 t 'flyspell-timer-idle-timer-function)))
(defvar flyspell-timer-long-idle-timer nil)
;;(setq flyspell-timer-disable-timers t)
(defvar flyspell-timer-disable-timers nil)
;; I don't understand why this is necessary but it seems to help the
;; slow idle timer work in the correct buffer. I suspect someother
;; timer is screwing up with the current buffer...
(defvar flyspell-timer-timer-buffer nil)
(defun flyspell-timer-idle-timer-function(&optional buffer)
;; so this only works on the current buffer. Might want to scavenge
;; over other buffers
(save-excursion
(set-buffer (or buffer flyspell-timer-timer-buffer
(window-buffer
(selected-window))))
(if (and flyspell-mode flyspell-timer-mode (not flyspell-timer-disable-timers))
(flyspell-timer-idle-timer-function-0))))
;; for some reason that I do not understand yet, this sometimes
;; appears to work in the wrong buffer. I really have not got any idea
;; why this is the case.
(defun flyspell-timer-idle-timer-function-0()
"Add all words to the buffer.
`flyspell-timer-scavenge-buffer' does this more efficiently interactively.
If this takes up too much processor power, see `flyspell-timer-scavenge-some-chunk-size'."
(interactive)
(let ((forward-marker (point))
(backward-marker (point))
;; Remember the current window positions...the movement in
;; this function will change these, keeping the cursor always
;; in the middle.
(forward-end (window-end))
(backward-start (window-start))
(forward-complete nil)
(backward-complete nil)
(repeat t))
(when flyspell-timer-idle-timer-verbose
(message "flyspell buffer..."))
(while
(and repeat
(not (and forward-complete backward-complete)))
(save-excursion
(unless backward-complete
(goto-char backward-marker)
(setq backward-marker
(flyspell-timer-check-words -1
flyspell-timer-chunk-size))
(setq backward-complete
(> backward-start backward-marker)))
(unless forward-complete
(goto-char forward-marker)
(setq forward-marker
(flyspell-timer-check-words 1 flyspell-timer-chunk-size))
(setq forward-complete
(< forward-end forward-marker))))
;; leave a gap so that we spam the cpu and there is plenty of
;; time for interruption
(setq repeat (sit-for flyspell-timer-delay))
(message "repeat %s" repeat))
(if flyspell-timer-idle-timer-verbose
(progn
(message "flyspell buffer...done")
(sit-for 2)
(message nil)))))
(defun flyspell-timer-check-words (&optional direction number)
"Check NUMBER words in DIRECTION. "
(if (not direction)
(setq direction 1))
(if (not number)
(setq number 5))
(save-excursion
(dotimes (i number)
(forward-word direction)
(flyspell-timer-word-maybe))
(point)))
(defun flyspell-timer-bounds-marked-p (start end)
"Return t if everywhere between START and END is marked."
(save-excursion
(let ((retn t))
(do ((i start (1+ i)))
((> i (- end 1)))
(unless (get-text-property i 'flyspell-added)
(setq retn nil)
(setq i end)))
retn)))
(defun flyspell-timer-word-maybe ()
"Add word in BOUNDS as abbreviation, and mark the buffer."
(let ((start (car (bounds-of-thing-at-point 'word)))
(end (cdr (bounds-of-thing-at-point 'word))))
;; has this word been checked already?
(unless
(flyspell-timer-bounds-marked-p start end)
;; set a property so that we know what we have done.
(flyspell-timer-save-buffer-modified-p
(add-text-properties start end
'(flyspell-added t)))
(flyspell-word)))
(if flyspell-timer-debug
(message "flyspell word %s" (word-at-point))))
(defmacro flyspell-timer-save-buffer-modified-p (&rest body)
"Eval BODY without affected buffer modification status"
`(let ((buffer-modified (buffer-modified-p)))
,@body
(set-buffer-modified-p buffer-modified)))
(provide 'flyspell-timer)
;;; flyspell-timer.el ends here