;;; lazy-lock.el --- Lazy demand-driven fontification for fast font-lock mode.

;; Copyright (C) 1994 Simon Marshall.

;; Author: Simon Marshall <simon@gnu.ai.mit.edu>
;; Keywords: faces files
;; Version: 1.10

;; LCD Archive Entry:
;; lazy-lock|Simon Marshall|simon@gnu.ai.mit.edu|
;; Lazy Font Lock mode (for fast demand-driven fontification).|
;; 25-Jan-95|1.10|~/modes/lazy-lock.el.Z|

;; The archive is archive.cis.ohio-state.edu in /pub/gnu/emacs/elisp-archive.

;;; This file is not part of GNU Emacs.

;;; 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 GNU Emacs; see the file COPYING.  If not, write to
;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

;;; #### various mods by Ben Wing; hopefully they will be merged into
;;; the next release of lazy-lock.  In particular, I changed things so
;;; that redisplay occurs *after* fontifying (following the model of
;;; fontifly), which seriously reduces eye-strain and annoying flashing.

;;; Commentary:

;; Purpose:
;;
;; To make visiting buffers in `font-lock-mode' faster by making fontification
;; be demand-driven and stealthy.
;; Fontification only occurs when, and where, necessary.
;;
;; See caveats and feedback below.
;; See also the face-lock package.
;; See also the fast-lock package.  (But don't use the two at the same time!)

;; Installation:
;; 
;; Put this file somewhere where Emacs can find it (i.e., in one of the paths
;; in your `load-path'), `byte-compile-file' it, and put in your ~/.emacs:
;;
;; (autoload 'turn-on-lazy-lock "lazy-lock"
;;   "Unconditionally turn on Lazy Lock mode.")
;;
;; (add-hook 'font-lock-mode-hook 'turn-on-lazy-lock)
;;
;; Start up a new Emacs and use font-lock as usual (except that you can use the
;; so-called "gaudier" fontification regexps on big files without frustration).
;;
;; In a buffer (which has `font-lock-mode' enabled) which is at least
;; `lazy-lock-minimum-size' characters long, only the visible portion of the
;; buffer will be fontified.  Motion around the buffer will fontify those
;; visible portions that were not previous fontified.  Text that has not yet
;; been fontified is displayed in `lazy-lock-invisible-foreground'.
;;
;; If stealth fontification is enabled (in Emacs 19.26 and up), fontification
;; will occur in invisible parts of the buffer after `lazy-lock-stealth-time'
;; seconds of Emacs idle time (i.e., there is no input during that time).

;; Advanced Use:
;;
;; You can also do fancy things with `advice'.  For example, to fontify while
;; pausing when dragging the scroll-bar, you could put in your ~/.emacs:
;;
;; (defadvice scroll-bar-drag-1 (after lazy-lock-fontify activate compile)
;;   "Fontify while scrolling with \\[scroll-bar-drag].
;; Fontifies if there is no further scrolling after `lazy-lock-delay-time'."
;;   (and (boundp 'lazy-lock-mode) lazy-lock-mode
;;        (let ((this-command (car lazy-lock-delay-commands)))
;;          (lazy-lock-post-command-hook))))
;;
;; To prevent large insertions from being fontified entirely, something like:
;;
;; (defadvice font-lock-after-change-function (around lazy-lock-fontify
;;                                             activate compile)
;;   "Fontify selectively when inserting."
;;   (if (and (pos-visible-in-window-p beg) (pos-visible-in-window-p end))
;;       ad-do-it
;;     (let ((this-command 'ignore))
;;       (put-text-property beg end 'fontified nil)
;;       (lazy-lock-fontify-window))))
;;
;; (Note that the above code fragments seemed to work when I wrote them---under
;; Emacs 19.25.  You might need to modify them.  Please let me know if you do.)
;;
;; If you are going to use `advice', you could also put in your ~/.emacs:
;;
;; (defadvice font-lock-mode (after lazy-lock-mode activate compile)
;;   "Turn off Lazy Lock mode when Font Lock mode is turned off."
;;   (or font-lock-mode (not lazy-lock-mode) (lazy-lock-mode -1)))
;;
;; These kinds of things with `advice' aren't done automatically because they
;; cause large packages (advice.el plus bytecomp.el and friends) to be loaded.

;; Caveats:
;;
;; This is "The Other Idea" for speeding up Font Lock mode.  The idea is to
;; only fontify when, and where, absolutely necessary.  "When" means fontify
;; just as the region concerned becomes visible, and "where" means fontify just
;; the visible region where not previously fontified.
;;
;; It's an idea I didn't originally pursue (I developed fast-lock instead) as
;; Emacs 19.25 didn't provide the functionality to properly support it, and rms
;; didn't feel that it was a "solid" solution to speeding up Font Lock mode in
;; the sense that you might expect to find text properties where none were yet
;; generated (e.g., in a region containing an as yet unfontified section).  But
;; then again now I use Lazy Lock mode rather than Fast Lock mode!
;;
;; This was originally an exercise to see how effective the concept behaves;
;; it is implemented by putting the function `lazy-lock-post-command-hook' on
;; Emacs' `post-command-hook' so that fontification occurs after commands have
;; completed.  This is not the most efficient way of doing it, and not the
;; "correct" way (as of Emacs 19.25 the "correct" way is not possible).

;; Known Problems/Bugs/Features:
;;
;; Lazy Lock mode does not work efficiently with Outline mode.  This is because
;; when in Outline mode, although text may be hidden (not visible in the
;; window), the text is visible to Emacs Lisp code (not surprisingly) and Lazy
;; Lock and fontifies it mercilessly.  Hopefully this will be fixed for 1.11.
;;
;; Unless otherwise stated, "Emacs 19.X" means versions up to and including X.
;;
;; Redisplay occurs *before* refontification.  This is because Emacs 19.25 runs
;; `post-command-hook' before certain redisplays occur, and redisplays are
;; needed so that `window-start' and `window-end' have realistic values.
;;
;; In Emacs 19.25, one `window-start'/`window-end' bug means that if you open a
;; file in another frame (such as via `find-tag-other-frame'), the whole buffer
;; is fontified regardless.  Upgrade!
;;
;; In Emacs 19.25, fontification by stealth is turned off because of a fatal
;; bug in `previous-single-property-change'.  Upgrade!
;;
;; In Emacs 19.28, if you see a message in the minibuffer of the form
;; "Fontifying window... done.  (Restarted in foo.c)"
;; it means the Garbage Collector has marked some (subsequently used) text
;; properties.  Lazy Lock attempts to recover the situation by restarting in
;; that buffer.  Unfortunately, that buffer will be left in a writable and
;; modified state.  Also, other windows may not be fontified when this happens.
;; To reduce the frequency of this bug occuring, increase in your ~/.emacs the
;; value of `gc-cons-threshold' to, say, 1Meg, e.g.:
;;
;; (setq gc-cons-threshold (* 1024 1024))
;;
;; The solution is to upgrade!  (With thanks to Kevin Broadey for help here.)
;;
;; For XEmacs 19.11 and Lucid Emacs 19.10 users, lazy-lock sort-of works.
;; There are bugs in text property and point/window primatives.  Upgrade!

;; Feedback:
;;
;; Feedback is welcome.  Or just take the idea/code and run with it.
;; To submit a bug report (or make comments) please use the mechanism provided:
;;
;; M-x lazy-lock-submit-bug-report RET

;; History:
;;
;; 0.01--1.00:
;; - Changed name from fore-lock to lazy-lock.  Shame though.
;; - Dropped `advice'-wrapping completely.  Ask me if you're interested in it.
;; - Made `lazy-lock-mode' ignore `post-command-hook' and `buffer-file-name'.
;; - Made `lazy-lock-fontify-window' check `lazy-lock-mode' and `this-command'.
;; - Made `lazy-lock-fontify-window' redisplay via `sit-for'.
;; - Added `lazy-lock-minimum-size' to control `lazy-lock-mode'.
;; 1.00--1.01:
;; - Added `lazy-lock-fontify-buffer'.
;; - Made `lazy-lock-fontify-window' ignore `lazy-lock-mode'.
;; - Made `lazy-lock-fontify-window' suspicious of `window-' favourites again.
;; - Added `lazy-lock-delay-commands' (idea from William G. Dubuque).
;; - Added `lazy-lock-ignore-commands' for completeness.
;; - Added `lazy-lock-continuity-time' for normal input delay.
;; 1.01--1.02:
;; - Made `lazy-lock-fontify-window' cope with multiple unfontified regions.
;; - Made `lazy-lock-mode' remove `fontified' properties if turned off.
;; - Made `lazy-lock-fontify-window' fontify by lines.
;; - Added `lazy-lock-cache-position' buffer local to detect visibility change.
;; - Added `lazy-lock-post-command-hook' to do the waiting.
;; - Made `lazy-lock-fontify-window' just do the fontification.
;; - Made `lazy-lock-mode' append `lazy-lock-post-command-hook'.
;; - Added `lazy-lock-walk-windows' to hack multi-window motion.
;; - Made `lazy-lock-post-command-hook' `walk-windows' if variable is non-nil.
;; - Removed `lazy-lock-ignore-commands' since insertion may change window.
;; - Added `lazy-lock-fontify-stealthily' and `lazy-lock-stealth-time'.
;; - Made `lazy-lock-post-command-hook' use them.
;; 1.02--1.03:
;; - Made `lazy-lock-fontify-stealthily' do `forward-line' not `previous-line'.
;; - Made `lazy-lock-fontify-stealthily' `move-to-window-line' first.
;; - Made `lazy-lock-fontify-stealthily' use `text-property-any' for region.
;; - Made `lazy-lock-post-command-hook' loop on `lazy-lock-fontify-stealthily'.
;; 1.03--1.04:
;; - Made `lazy-lock-mode' reset `lazy-lock-cache-position'.
;; - Made `lazy-lock-post-command-hook' `widen' for `if' `text-property-any'.
;; - Made `lazy-lock-fontify-stealthily' return `text-property-any'.
;; - Added `lazy-lock-percent-fontified' for a/be-musement.
;; - Made `lazy-lock-post-command-hook' use it.
;; - Made `lazy-lock-mode' use `make-local-hook' etc. if available.
;; - Made `lazy-lock-mode' use `before-revert-hook' and `after-revert-hook'.
;; - Made `lazy-lock-post-command-hook' protect `deactivate-mark'.
;; - Adds `lazy-lock-post-command-hook' globally to `post-command-hook'.
;; 1.04--1.05:
;; - Made `lazy-lock-mode' test `make-local-hook' not `emacs-minor-version'.
;; 1.05--1.06:
;; - Added `lazy-lock-ignore-commands' for commands that leave no event but do.
;; - Made `lazy-lock-post-command-hook' check `lazy-lock-ignore-commands'.
;; 1.06--1.07:
;; - Removed `before-revert-hook' and `after-revert-hook' use.
;; 1.07--1.08:
;; - Added `lazy-lock-submit-bug-report'.
;; - Made `lazy-lock-post-command-hook' check `executing-macro'.
;; - Made it sort-of/almost work for XEmacs (help from Jonas Jarnestrom).
;; - XEmacs: Fix `text-property-not-all' (fix based on fast-lock.el 3.05 fix).
;; - XEmacs: Set `font-lock-no-comments' and alias `frame-parameters'.
;; - Made `byte-compile-warnings' omit `unresolved' on compilation.
;; - Made `lazy-lock-post-command-hook' protect `buffer-undo-list'.
;; - Moved `deactivate-mark' and `buffer-undo-list' protection to functions.
;; - Added `lazy-lock-invisible-foreground' (idea from Boris Goldowsky).
;; - XEmacs: Fix to use `text-property-not-all' t, not `text-property-any' nil.
;; - Made `lazy-lock-percent-fontified' return `round' to an integer.
;; - XEmacs: Fix `text-property-any' (fix and work around for a bug elsewhere).
;; - XEmacs: Fix `lazy-lock-submit-bug-report' for reporter.el & vm-window.el.
;; - XEmacs: Made `lazy-lock-fontify-window' loop `while' `<' not `/='.
;; - Use `font-lock-after-change-function' to do the fontification.
;; 1.08--1.09:
;; - Made `lazy-lock-post-command-hook' protect with `condition-case'.
;; - Made `lazy-lock-cache-start' to cache `window-start'.
;; - Made `lazy-lock-fontify-window' check and cache `lazy-lock-cache-start'.
;; - Renamed `lazy-lock-cache-position' to `lazy-lock-cache-end'.
;; - XEmacs: Fix for `font-lock-after-change-function'.
;; - Adds `lazy-lock-post-command-hook' globally to `window-setup-hook'.
;; 1.09--1.10:
;; - Made `buffer-file-name' be `let' to prevent supersession (Kevin Broadey).
;; - Made `lazy-lock-submit-bug-report' `require' reporter (Ilya Zakharevich).
;; - Made `lazy-lock-mode' and `turn-on-lazy-lock' succeed `autoload' cookies.
;; - Added `lazy-lock-fontify-walk-windows' for walking window fontification.
;; - Added `lazy-lock-fontify-walk-stealthily' for walking stealth.
;; - Removed `move-to-window-line' from `lazy-lock-fontify-stealthily'.
;; - Added `lazy-lock-unfontified-p' to indicate fontification remaining.
;; - Made `lazy-lock-percent-fontified' use `truncate' rather than `round'.
;; - Added other `*-argument' to `lazy-lock-ignore-commands' (Kevin Broadey).
;; - Made `lazy-lock-fontify-stealthily' not assume buffer is part `fontified'.
;; - Emacs: Fix for `font-lock-fontify-region'.
;; - Made `lazy-lock-post-command-hook' check for minibuffer (Kevin Broadey).
;; - Added `lazy-lock-stealth-nice' for niceness during stealth fontification.
;; - Added `lazy-lock-stealth-lines' for chunks of stealth fontification.

;;; Code:
(require 'font-lock)

(eval-when-compile
  ;; Shut Emacs' byte-compiler up (cf. stop me getting mail from users).
  (setq byte-compile-warnings '(free-vars callargs redefine)))

(defun lazy-lock-submit-bug-report ()
  "Submit via mail a bug report on lazy-lock.el."
  (interactive)
  (require 'reporter)
  (let ((reporter-prompt-for-summary-p t))
    (reporter-submit-bug-report "simon@gnu.ai.mit.edu" "lazy-lock 1.10"
     '(lazy-lock-walk-windows
       lazy-lock-continuity-time lazy-lock-delay-time
       lazy-lock-stealth-time lazy-lock-stealth-nice lazy-lock-stealth-lines
       lazy-lock-delay-commands lazy-lock-ignore-commands
       lazy-lock-minimum-size lazy-lock-invisible-foreground)
     nil nil
     (concat "Hi Si.,

I want to report a bug.  I've read the `Bugs' section of `Info' on Emacs, so I
know how to make a clear and unambiguous report.  To reproduce the bug:

Start a fresh Emacs via `" invocation-name " -no-init-file -no-site-file'.
In the `*scratch*' buffer, evaluate:"))))

;; Variables:

(defconst lazy-lock-running-xemacs-p
  (not (null (save-match-data (string-match "Lucid" emacs-version)))))

(defconst lazy-lock-emacs-minor-version
  (if (boundp 'emacs-minor-version) emacs-minor-version 0))

(defvar lazy-lock-verbose nil
  "*If non-nil, lazy-lock displays messages as it works.")

(defvar lazy-lock-minimum-size (* 10 1024)
  "*If non-nil, the minimum size for buffers.
Only buffers at least this size in bytes can have demand-driven fontification.
If nil, means size is irrelevant.")

(defvar lazy-lock-walk-windows t
  "If non-nil, fontify windows other than the selected window.
If `all-frames', fontify windows even on other frames.
A non-nil value slows down redisplay.")

(defvar lazy-lock-continuity-time
  ;; (sit-for 0) doesn't work like you'd expect it to in XEmacs because
  ;; of bugs in the Xt event loop (blame Xt for this, not XEmacs).
  ;; XEmacs enforces a millisecond granularity on sit-for times so
  ;; we use 0.001.
  (if lazy-lock-running-xemacs-p 0.001 0)
  "*Time in seconds to delay before normal window fontification.
Window fontification occurs if there is no input within this time.")

;; `previous-single-property-change' at `point-min' up to Emacs 19.25 is fatal.
;; `text-property-any' and `text-property-not-all' are too broke in XEmacs
;; up to 19.11.
(defvar lazy-lock-stealth-time
  (if (or (and lazy-lock-running-xemacs-p (> lazy-lock-emacs-minor-version 11))
	  (> lazy-lock-emacs-minor-version 25))
      30
    nil)
  "*Time in seconds to delay before beginning stealth fontification.
Stealth fontification occurs if there is no input within this time.
If nil, means no fontification by stealth.")

(defvar lazy-lock-stealth-lines
  (if (or lazy-lock-running-xemacs-p (> lazy-lock-emacs-minor-version 28))
      150 50)
  "*If non-nil, the maximum size of a chunk of stealth fontification.
Each iteration of stealth fontification can fontify this number of lines.
If nil, means use `window-height' for the maximum chunk size.")

(defvar lazy-lock-stealth-nice (if (featurep 'lisp-float-type) 0.125 1)
  "*Time in seconds to pause during chunks of stealth fontification.
The higher the value, the longer stealth fontification takes and the lower the
load made by Emacs on the machine while it takes place.")

;; Bill Dubuque says "assume 33 chars-per-second auto-repeat"
;; Simon had this set to 2 secs always, but that seems way overboard
(defvar lazy-lock-delay-time (if (featurep 'lisp-float-type) 0.03 2)
  "*Time in seconds to delay before special window fontification.
Special fontification occurs after one of `lazy-lock-delay-commands'.")

(defvar lazy-lock-delay-commands
  '(isearch-printing-char isearch-repeat-forward isearch-repeat-backward)
  "A list of commands after which fontification should delay.
Fontification occurs if there is no input after `lazy-lock-delay-time'.")

(defvar lazy-lock-ignore-commands
  '(universal-argument digit-argument negative-argument
    isearch-other-control-char isearch-other-meta-char)
  "A list of commands after which fontification should not occur.")

(defvar lazy-lock-invisible-foreground
  (cond ((fboundp 'valid-color-name-p)
	 (if (valid-color-name-p "gray50") "gray50"))
	((fboundp 'x-valid-color-name-p)
	 (if (x-valid-color-name-p "gray50") "gray50"))
	((fboundp 'x-color-defined-p)
	 (if (x-color-defined-p "gray50") "gray50")))
  "The foreground colour to use to display invisible text.
If nil, the default foreground is used.  If t, the default background is used.
If a string, it should be a colour to use (either its name or its RGB value).
Invisible text is momentarily seen when scrolling into unfontified regions.")

(defvar lazy-lock-mode nil)		; for modeline

;; These variables record, for each buffer, the window start and end positions.
;; If these have not changed, the displayed window must be the same as before.
;; Used to make window fontification faster by avoiding text property searches.
(defvar lazy-lock-cache-start)
(make-variable-buffer-local 'lazy-lock-cache-start)
(defvar lazy-lock-cache-end)
(make-variable-buffer-local 'lazy-lock-cache-end)

;; Commands:

;;;###autoload
(defun lazy-lock-mode (&optional arg)
  "Toggle Lazy Lock mode.
With arg, turn Lazy Lock mode on if and only if arg is positive and the buffer
is at least `lazy-lock-minimum-size' characters long.

When Lazy Lock mode is enabled, fontification is demand-driven and stealthy:

 - Fontification occurs in visible parts of buffers when necessary.
   Occurs if there is no input after pausing for `lazy-lock-continuity-time'.

 - Fontification occurs in invisible parts when Emacs has been idle.
   Occurs if there is no input after pausing for `lazy-lock-stealth-time'.

Text is displayed in `lazy-lock-invisible-foreground' until fontified.
Use \\[lazy-lock-fontify-buffer] to fontify the whole buffer.

See also variables `lazy-lock-walk-windows', `lazy-lock-stealth-lines',
`lazy-lock-stealth-nice', `lazy-lock-delay-time', `lazy-lock-delay-commands'
and `lazy-lock-ignore-commands'.

Use \\[lazy-lock-submit-bug-report] to send bug reports or feedback."
  (interactive "P")
  (set (make-local-variable 'lazy-lock-mode)
       (and (<= (or lazy-lock-minimum-size 0) (buffer-size))
	    (if (null arg)
		(not lazy-lock-mode)
	      (> (prefix-numeric-value arg) 0))))
  (if (not lazy-lock-mode)
      (let ((modified (buffer-modified-p)) (inhibit-read-only t)
	    (buffer-undo-list t) deactivate-mark buffer-file-name)
	(remove-text-properties (point-min) (point-max) '(fontified nil))
	(or modified (set-buffer-modified-p nil)))
    (if lazy-lock-invisible-foreground (lazy-lock-colour-buffer))
    (setq lazy-lock-cache-start 0 lazy-lock-cache-end 0)
    (set (make-local-variable 'font-lock-fontified) t))
  ;; if some sod makes post-command-hook local ...
  (and lazy-lock-mode
       (or (memq 'lazy-lock-post-command-hook post-command-hook)
	   (add-hook 'post-command-hook 'lazy-lock-post-command-hook t))))

;;;###autoload
(defun turn-on-lazy-lock ()
  "Unconditionally turn on Lazy Lock mode."
  (lazy-lock-mode 1))

(defun lazy-lock-fontify-buffer ()
  "Fontify the current buffer the way `font-lock-mode' would.
Completely fontifies the entire buffer."
  (interactive)
  ;; Could do only where not `fontified' before, but it might not be worth it.
  (let ((modified (buffer-modified-p)) (inhibit-read-only t)
	(buffer-undo-list t) deactivate-mark buffer-file-name)
    (font-lock-fontify-buffer)
    (put-text-property (point-min) (point-max) 'fontified t)
    (or modified (set-buffer-modified-p nil))))

;; Functions:

(defun lazy-lock-post-command-hook ()
  ;; Do groovy things if (a) we're not in a macro, (b) we've got real command,
  ;; and (c) we're not in the minibuffer.
  (if (and (not executing-macro)
	   (not (memq this-command lazy-lock-ignore-commands))
	   (not (window-minibuffer-p (selected-window)))
	   (if (memq this-command lazy-lock-delay-commands)
	       (sit-for lazy-lock-delay-time)
	     (sit-for lazy-lock-continuity-time t)))
      (progn
	;;
	;; Do the visible parts of the buffer(s), i.e., the window(s).
	(if (or (not lazy-lock-walk-windows)
		(and (eq lazy-lock-walk-windows t) (one-window-p t)))
	    (if lazy-lock-mode (lazy-lock-fontify-window))
	  (lazy-lock-fontify-walk-windows))
	;;
	;; Do the invisible parts of buffers.
	(if (and lazy-lock-stealth-time (sit-for lazy-lock-stealth-time))
	    (lazy-lock-fontify-walk-stealthily)))))

;; based (without permission -- sorry Bill!) on fontifly
(defun lazy-lock-find-bol (&optional point forward)
  (let ((original-point (point)))
    (prog1
	(progn
	  (if point (goto-char point))
	  (beginning-of-line)
	  (if forward (forward-line forward))
	  (point))
      (goto-char original-point))))

(defun lazy-lock-fontify-window ()
  ;; Fontify the visible part of the buffer where necessary.
  ;; We rely on `window-start' and `window-end' to have reasonable values.
  ;; Only fontify if `window-start' or `window-end' have changed.
  (let (;(ws (min (max (window-start) (point-min)) (point-max)))
	;(we (min (max (1- (window-end)) (point-min)) (point-max)))
	(ws (lazy-lock-find-bol (point) (- (window-height))))
	(we (lazy-lock-find-bol (point) (window-height))))
    (if (or (/= ws lazy-lock-cache-start) (/= we lazy-lock-cache-end))
	;; Find where we haven't `fontified' before.
	(let* ((start (or (text-property-not-all ws we 'fontified t) ws))
	       (end (or (text-property-any start we 'fontified t) we))
	       (modified (buffer-modified-p)) (inhibit-read-only t)
	       ;; We do the following to prevent: undo list addition; region
	       ;; highlight disappearance; supersession/locking checks.
	       (buffer-undo-list t) deactivate-mark buffer-file-name)
	  (while (< start end)
	    (if lazy-lock-verbose (message "Fontifying window..."))
	    ;; Fontify and flag the region as `fontified'.
	    (font-lock-after-change-function start end 0)
	    (put-text-property start end 'fontified t)
	    ;; Find the next region.
	    (setq start (or (text-property-not-all ws we 'fontified t) ws)
		  end (or (text-property-any start we 'fontified t) we))
	    (if lazy-lock-verbose (message "Fontifying window... done.")))
	  (setq lazy-lock-cache-start ws lazy-lock-cache-end we)
	  (or modified (set-buffer-modified-p nil))))))

(defun lazy-lock-fontify-walk-windows ()
  ;; Fontify windows in all required by walking through them.
  (save-window-excursion
    (condition-case nil
	(walk-windows
	 (function (lambda (window)
		     (select-window window)
		     (if lazy-lock-mode (lazy-lock-fontify-window))))
	 'no-minibuf (eq lazy-lock-walk-windows 'all-frames))
      (wrong-type-argument
       ;; Looks like the 19.28 Garbage Collection bug has hit town.
       ;; Completely remove all text properties and restart lazy-lock.
       (set-text-properties (point-min) (point-max) nil)
       (turn-on-lazy-lock)
       (lazy-lock-fontify-window)
       (message "Fontifying window... done.  (Restarted in %s)"
		(buffer-name))))))

(defun lazy-lock-fontify-stealthily ()
  ;; Fontify an invisible part of the buffer where necessary.
  ;; Fontifies at most `window-height' lines.
  ;; Find where the next and previous regions not `fontified' begin and end.
  (let ((next (next-single-property-change (point) 'fontified))
	(prev (previous-single-property-change (point) 'fontified))
	(modified (buffer-modified-p)) (inhibit-read-only t)
	(buffer-undo-list t) deactivate-mark buffer-file-name start end)
    (save-excursion
      (save-restriction
	(widen)
	(cond ((and (null next) (null prev))
	       ;; Nothing has been `fontified' yet.
	       (beginning-of-line 1) (setq start (point))
	       (forward-line (or lazy-lock-stealth-lines (window-height)))
	       (setq end (point)))
	      ((or (null prev)
		   (and next (> (- (point) prev) (- next (point)))))
	       ;; The next region is the nearest not `fontified'.
	       (goto-char next) (beginning-of-line 1) (setq start (point))
	       (forward-line (or lazy-lock-stealth-lines (window-height)))
	       ;; Maybe the region is already partially `fontified'.
	       (setq end (or (text-property-any next (point) 'fontified t)
			     (point))))
	      (t
	       ;; The previous region is the nearest not `fontified'.
	       (goto-char prev) (forward-line 1) (setq end (point))
	       (forward-line (- (or lazy-lock-stealth-lines (window-height))))
	       ;; Maybe the region is already partially `fontified'.
	       (setq start (text-property-not-all (point) end 'fontified t))))
	;; Fontify and flag the region as `fontified'.
	(font-lock-after-change-function start end 0)
	(put-text-property start end 'fontified t)
	(or modified (set-buffer-modified-p nil))))))

(defun lazy-lock-fontify-walk-stealthily ()
  ;; Fontify regions in all required buffers while there is no input.
  (let ((buffers (buffer-list)) (continue t) (fontified nil))
    (save-excursion
      (while (and buffers continue)
	(set-buffer (car buffers))
	(if (and lazy-lock-mode (lazy-lock-unfontified-p))
	    ;; Fontify regions in this buffer while there is no input.
	    (let ((bufname (buffer-name)))
	      (if (and lazy-lock-verbose (not fontified))
		  (message "Fontifying stealthily..."))
	      (lazy-lock-fontify-stealthily)
	      (while (and (lazy-lock-unfontified-p)
			  (setq continue (sit-for lazy-lock-stealth-nice)))
		(if lazy-lock-verbose
		    (message "Fontifying stealthily... %2d%% of %s"
			     (lazy-lock-percent-fontified) bufname))
		(lazy-lock-fontify-stealthily))
	      ;; Note that fontification occurred.
	      (setq fontified t)))
	(setq buffers (cdr buffers))))
    (if (and lazy-lock-verbose fontified)
	(message "Fontifying stealthily... %s." (if continue "done" "quit")))))

(defun lazy-lock-unfontified-p ()
  ;; Return non-nil if there is anywhere still to be `fontified'.
  (save-restriction
    (widen)
    (text-property-not-all (point-min) (point-max) 'fontified t)))

(defun lazy-lock-percent-fontified ()
  ;; Return the percentage (of characters) of the buffer that are `fontified'.
  (save-restriction
    (widen)
    (let ((size 0) (start (point-min)) (max (point-max)) end)
      (while (setq start (text-property-any start max 'fontified t))
	(setq end (or (text-property-not-all start max 'fontified t) max)
	      size (+ size (- end start))
	      start end))
      ;; Saying "99% done" is probably better than "100% done" when it isn't.
      (truncate (/ (* size 100.0) (buffer-size))))))

(defun lazy-lock-colour-buffer ()
  ;; Set the `face' of all text in the buffer to `lazy-lock-invisible-face'.
  (save-restriction
    (widen)
    (let ((face 'lazy-lock-invisible-face)
	  (fore (if (stringp lazy-lock-invisible-foreground)
		    lazy-lock-invisible-foreground
		  (cdr (assq 'background-color (frame-parameters)))))
	  (modified (buffer-modified-p)) (inhibit-read-only t)
	  (buffer-undo-list t) deactivate-mark buffer-file-name)
      (make-face face)
      (or (equal (face-foreground face) fore) (set-face-foreground face fore))
      (put-text-property (point-min) (point-max) 'face face)
      (put-text-property (point-min) (point-max) 'fontified nil)
      (or modified (set-buffer-modified-p nil)))))

;; Functions for Emacs:

;; This fix is for a number of bugs in the function in Emacs 19.28.
(if (and (not lazy-lock-running-xemacs-p)
	 (< lazy-lock-emacs-minor-version 29))
    (defun font-lock-fontify-region (start end &optional loudly)
      "Put proper face on each string and comment between START and END."
      (save-excursion
	(save-restriction
	  (widen)
	  (goto-char start)
	  (beginning-of-line)
	  (if loudly (message "Fontifying %s... (syntactically...)" (buffer-name)))
	  (let ((buffer-undo-list t) (inhibit-read-only t)
		(modified (buffer-modified-p))
		(synstart (if comment-start-skip
			      (concat "\\s\"\\|" comment-start-skip)
			    "\\s\""))
		(comstart (if comment-start-skip
			      (concat "\\s<\\|" comment-start-skip)
			    "\\s<"))
		(startline (point))
		state prev prevstate)
	    ;; Find the state at the line-beginning before START.
	    (if (eq startline font-lock-cache-position)
		(setq state font-lock-cache-state)
	      ;; Find outermost containing sexp.
	      (beginning-of-defun)
	      ;; Find the state at STARTLINE.
	      (while (< (point) startline)
		(setq state (parse-partial-sexp (point) startline 0)))
	      (setq font-lock-cache-state state
		    font-lock-cache-position (point)))
	    ;; Now find the state precisely at START.
	    (setq state (parse-partial-sexp (point) start nil nil state))
	    ;; If the region starts inside a string, show the extent of it.
	    (if (nth 3 state)
		(let ((beg (point)))
		  (while (and (re-search-forward "\\s\"" end 'move)
			      (nth 3 (parse-partial-sexp beg (point) nil nil
							 state))))
		  (put-text-property beg (point) 'face font-lock-string-face)
		  (setq state (parse-partial-sexp beg (point) nil nil state))))
	    ;; Likewise for a comment.
	    (if (or (nth 4 state) (nth 7 state))
		(let ((beg (point)))
		  (save-restriction
		    (narrow-to-region (point-min) end)
		    (condition-case nil
			(progn
			  (re-search-backward comstart (point-min) 'move)
			  (forward-comment 1)
			  ;; forward-comment skips all whitespace,
			  ;; so go back to the real end of the comment.
			  (skip-chars-backward " \t"))
		      (error (goto-char end))))
		  (put-text-property beg (point) 'face font-lock-comment-face)
		  (setq state (parse-partial-sexp beg (point) nil nil state))))
	    ;; Find each interesting place between here and END.
	    (while (and (< (point) end)
			(setq prev (point) prevstate state)
			(re-search-forward synstart end t)
			(progn
			  ;; Clear out the fonts of what we skip over.
			  (remove-text-properties prev (point) '(face nil))
			  ;; Verify the state at that place
			  ;; so we don't get fooled by \" or \;.
			  (setq state (parse-partial-sexp prev (point) nil nil
							  state))))
	      (let ((here (point)))
		(if (or (nth 4 state) (nth 7 state))
		    ;; We found a real comment start.
		    (let ((beg (match-beginning 0)))
		      (goto-char beg)
		      (save-restriction
			(narrow-to-region (point-min) end)
			(condition-case nil
			    (progn
			      (forward-comment 1)
			      ;; forward-comment skips all whitespace,
			      ;; so go back to the real end of the comment.
			      (skip-chars-backward " \t"))
			  (error (goto-char end))))
		      (put-text-property beg (point) 'face font-lock-comment-face)
		      (setq state (parse-partial-sexp here (point) nil nil state)))
		  (if (nth 3 state)
		      (let ((beg (match-beginning 0)))
			(while (and (re-search-forward "\\s\"" end 'move)
				    (nth 3 (parse-partial-sexp here (point) nil nil
							       state))))
			(put-text-property beg (point) 'face font-lock-string-face)
			(setq state (parse-partial-sexp here (point) nil nil
							state))))))
	      ;; Make sure PREV is non-nil after the loop
	      ;; only if it was set on the very last iteration.
	      (setq prev nil))
	    (and prev
		 (remove-text-properties prev end '(face nil)))
	    (and (buffer-modified-p)
		 (not modified)
		 (set-buffer-modified-p nil)))))))

;; Functions for XEmacs:

;; text-property-any and text-property-not-all are highly broken in 
;; XEmacs up to 19.11.  The following definitions are taken from 19.12
;; and should work much better (although they may not work perfectly
;; because next-single-property-change is somewhat broken in 19.11 --
;; 19.12 has a correct version but it requires the primitive
;; `next-extent-change', which is not in 19.11 and is difficult to
;; write in Lisp.  This fix includes a work around which
;; prevents a bug in `window-start' causing a barf here, which is also
;; fixed in XEmacs 19.12.
(if (and lazy-lock-running-xemacs-p
	 (< lazy-lock-emacs-minor-version 12))

    (progn

      (defun text-property-any (start end prop value &optional buffer)
	"Check text from START to END to see if PROP is ever `eq' to VALUE.
If so, return the position of the first character whose PROP is `eq'
to VALUE.  Otherwise return nil."
	(let ((start (min start end)) (end (max start end)))
	  (while (and start (< start end)
		      (not (eq value (get-text-property start prop buffer))))
	    (setq start (next-single-property-change start prop buffer end)))
	  start))

      (defun text-property-not-all (start end prop value &optional buffer)
	"Check text from START to END to see if PROP is ever not `eq' to VALUE.
If so, return the position of the first character whose PROP is not
`eq' to VALUE.  Otherwise, return nil."
	(if (not (eq value (get-text-property start prop buffer)))
	    start
	  (next-single-property-change start prop buffer end)))))

;; XEmacs 19.11 function `font-lock-any-extents-p' looks for `text-prop' rather
;; than `face'.  Since `font-lock-unfontify-region' only removes `face', and we
;; have non-font-lock properties hanging about, `text-prop' never gets removed.
;; Unfortunately `font-lock-any-extents-p' is inlined so we can't redefine it.
(if (and lazy-lock-running-xemacs-p
	 (< lazy-lock-emacs-minor-version 12))
    (add-hook 'font-lock-mode-hook
     (function (lambda ()
	(remove-hook 'after-change-functions 'font-lock-after-change-function)
	(add-hook 'after-change-functions
	 (function (lambda (beg end len)
	    (let ((a-c-beg beg) (a-c-end end))
	      (save-excursion
		;; First set `text-prop' to nil for `font-lock-any-extents-p'.
		(goto-char end) (forward-line 1) (setq end (point))
		(goto-char beg) (beginning-of-line) (setq beg (point))
		(put-text-property beg end 'text-prop nil)
		;; Then do the real `font-lock-after-change-function'.
		(font-lock-after-change-function a-c-beg a-c-end len)
		;; Now set `fontified' to t to stop `lazy-lock-fontify-window'.
		(put-text-property beg end 'fontified t))))))))))

;; Cope with the differences between Emacs and [LX]Emacs.
(if lazy-lock-running-xemacs-p
    (progn
      (or (fboundp 'frame-parameters)
	  (defalias 'frame-parameters 'screen-parameters))
      (setq font-lock-no-comments nil)
      (make-variable-buffer-local 'font-lock-no-comments)
      (add-hook 'font-lock-mode-hook
       '(lambda ()
	  (setq font-lock-no-comments (not font-lock-use-syntax-tables))))))

;; Install ourselves:

(add-hook 'window-setup-hook
 '(lambda ()
    (let ((lazy-lock-walk-windows 'all-frames) (lazy-lock-continuity-time 0))
      (lazy-lock-post-command-hook)))
 t)

(or (assq 'lazy-lock-mode minor-mode-alist)
    (setq minor-mode-alist (cons '(lazy-lock-mode " Lazy") minor-mode-alist)))

(add-hook 'post-command-hook 'lazy-lock-post-command-hook t)

;; Provide ourselves:

(provide 'lazy-lock)

;;; lazy-lock.el ends here
