(margin): Change background to "gray" for mono (this is
[bpt/emacs.git] / lisp / hilit-chg.el
CommitLineData
e287d328
RS
1;;; hilit-chg.el --- minor mode displaying buffer changes with special face
2
3;; Copyright (C) 1998 Free Software Foundation, Inc.
4
08246f2e 5;; Author: Richard Sharman <rsharman@pobox.com>
e287d328
RS
6;; Keywords: faces
7
66236b77
KH
8;; This file is part of GNU Emacs.
9
e287d328
RS
10;; This program is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 2, or (at your option)
13;; any later version.
14
15;; This program is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs; see the file COPYING. If not, write to the
22;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23;; Boston, MA 02111-1307, USA.
24
25
26;;; Commentary:
27
28;; A minor mode: "Highlight Changes mode".
29;;
30
31;; Highlight Changes mode has 2 submodes: active and passive.
32;; When active, changes to the buffer are displayed in a different face.
33;; When passive, any existing displayed changes are saved and new ones
34;; recorded but are not displayed differently.
35;; Why active and passive? Having the changes visible can be handy when you
36;; want the information but very distracting otherwise. So, you can keep
37;; Highlight Changes mode in passive state while you make your changes, toggle
38;; it on to active mode to see them, then toggle it back off to avoid
39;; distraction.
40;;
41;; When active, changes are displayed in `highlight-changes-face'. When
42;; text is deleted, the following character is displayed in
43;; `highlight-changes-delete-face' face.
44;;
45;;
46;; You can "age" different sets of changes by using
47;; `highlight-changes-rotate-faces'. This rotates different through a series
48;; of different faces, so you can distinguish "new" changes from "older"
00e25b96 49;; changes. You can customize these "rotated" faces in two ways. You can
e287d328
RS
50;; either explicitly define each face by customizing
51;; `highlight-changes-face-list'. If, however, the faces differ from
52;; `highlight-changes-face' only in the foreground colour, you can simply set
53;; `highlight-changes-colours'. If `highlight-changes-face-list' is nil when
54;; the faces are required they will be constructed from
55;; `highlight-changes-colours'.
56;;
57;;
58;; When a Highlight Changes mode is on (either active or passive) you can go
59;; to the next or previous change with `highlight-changes-next-change' and
60;; `highlight-changes-previous-change'.
61;;
62;;
99f08df4
KH
63;; You can also use the command highlight-compare-with-file to show changes
64;; in this file compared with another file (typically the previous version
65;; of the file).
e287d328
RS
66;;
67;;
68;; There are currently three hooks run by `highlight-changes-mode':
69;; `highlight-changes-enable-hook' - is run when Highlight Changes mode
70;; is initially enabled for a buffer.
71;; `highlight-changes-disable-hook' - is run when Highlight Changes mode
72;; is turned off.
73;; `highlight-changes-toggle-hook' - is run each time `highlight-changes-mode'
74;; is called. Typically this is when
75;; toggling between active and passive
76;; modes. The variable
77;; `highlight-changes-mode' contains the new
78;; state (`active' or `passive'.)
79;;
80;;
81;;
82;; Example usage:
83;; (defun my-highlight-changes-enable-hook ()
84;; (add-hook 'local-write-file-hooks 'highlight-changes-rotate-faces)
85;; )
86;;
87;; (defun my-highlight-changes-disable-hook ()
88;; (remove-hook 'local-write-file-hooks 'highlight-changes-rotate-faces)
89;; )
90;;
91;; (add-hook 'highlight-changes-enable-hook 'my-highlight-changes-enable-hook)
92;; (add-hook 'highlight-changes-disable-hook
93;; 'my-highlight-changes-disable-hook)
94
95
96;; Explciit vs. Implicit
97;;
98
99;; Normally, Highlight Changes mode is turned on explicitly in a buffer.
100;;
101;; If you prefer to have it automatically invoked you can do it as
102;; follows.
103;;
104;; 1. Most modes have a major-hook, typically called MODE-hook. You
105;; can use `add-hook' to call `highlight-changes-mode'.
106;;
107;; Example:
108;; (add-hook 'c-mode-hook 'highlight-changes-mode)
109;;
110;; If you want to make it start up in passive mode (regardless of the
111;; setting of highlight-changes-initial-state):
112;; (add-hook 'emacs-lisp-mode-hook
113;; (lambda ()
114;; (highlight-changes-mode 'passive)))
115;;
116;; However, this cannot be done for Fundamental mode for there is no
117;; such hook.
118;;
119;; 2. You can use the function `global-highlight-changes'
120;;
121;; This function, which is fashioned after the way `global-font-lock' works,
122;; toggles on or off global Highlight Changes mode. When activated, it turns
123;; on Highlight Changes mode in all "suitable" existings buffers and will turn
124;; it on in new "suitable" buffers to be created.
125;;
126;; A buffer's "suitability" is determined by variable
127;; `highlight-changes-global-modes', as follows. If the variable is
128;; * nil -- then no buffers are suitable;
129;; * a function -- this function is called and the result is used. As
130;; an example, if the value is 'buffer-file-name then all buffers
131;; who are visiting files are suitable, but others (like dired
132;; buffers) are not;
133;; * a list -- then if the buufer is suitable iff its mode is in the
134;; list, exccept if the first element is nil in which case the test
135;; is reversed (i.e. it is a list of unsuitable modes).
136;; * Otherwise, the buffer is suitable if its name does not begin with
137;; ` ' or `*' and if `buffer-file-name' returns true.
138;;
139
140
141
142;; Possible bindings:
143;; (global-set-key '[C-right] 'highlight-changes-next-change)
144;; (global-set-key '[C-left] 'highlight-changes-previous-change)
145;;
146;; Other interactive functions (which could be bound if desired):
147;; highlight-changes-mode
148;; highlight-changes-remove-highlight
149;; highlight-changes-rotate-faces
99f08df4 150;; highlight-compare-with-file
e287d328
RS
151
152;;
153;; You can automatically rotate faces when the buffer is saved;
154;; see function `highlight-changes-rotate-faces' for how to do this.
155;;
156
157
158;;; Bugs:
159
160;; - the next-change and previous-change functions are too literal;
161;; they should find the next "real" change, in other words treat
162;; consecutive changes as one.
163
164
165;;; To do (maybe), notes, ...
166
167;; - having different faces for deletion and non-deletion: is it
168;; really worth the hassle?
169;; - should have better hooks: when should they be run?
99f08df4
KH
170;; - highlight-compare-with-file should allow RCS files - e.g. nice to be
171;; able to say show changes compared with version 2.1.
e287d328
RS
172;; - Maybe we should have compare-with-buffer as well. (When I tried
173;; a while back I ran into a problem with ediff-buffers-internal.)
174
175
176;;; History:
177
178;; R Sharman (rsharman@magma.ca) Feb 1998:
179;; - initial release as change-mode.
e287d328
RS
180;; Jari Aalto <jari.aalto@ntc.nokia.com> Mar 1998
181;; - fixes for byte compile errors
182;; - use eval-and-compile for autoload
183;; Marijn Ros <J.M.Ros@fys.ruu.nl> Mar 98
184;; - suggested turning it on by default
e287d328
RS
185;; Eric Ludlam <zappo@gnu.org> Suggested using overlays.
186;; July 98
187;; - global mode and various stuff added
188;; - Changed to use overlays
189;; August 98
190;; - renmaed to Highlight Changes mode.
191
192
193;;; Code:
194
e287d328
RS
195(require 'wid-edit)
196
197;; ====================== Customization =======================
198(defgroup highlight-changes nil
199 "Highlight Changes mode."
a7845785 200 :version "20.4"
e287d328
RS
201 :group 'faces)
202
203
204;; Face information: How the changes appear.
205
206;; Defaults for face: red foreground, no change to background,
207;; and underlined if a change is because of a deletion.
208;; Note: underlining is helpful in that is shows up changes in white space.
209;; However, having it set for non-delete changes can be annoying because all
210;; indentation on inserts gets underlined (which can look pretty ugly!).
211
212(defface highlight-changes-face
213 '((((class color)) (:foreground "red" ))
214 (t (:inverse-video t)))
215 "Face used for highlighting changes."
216 :group 'highlight-changes
217 )
218
219;; This looks pretty ugly, actually. Maybe the underline should be removed.
220(defface highlight-changes-delete-face
221 '((((class color)) (:foreground "red" :underline t))
222 (t (:inverse-video t)))
223 "Face used for highlighting deletions."
224 :group 'highlight-changes
225 )
226
227
228
229;; A (not very good) default list of colours to rotate through.
230;;
231(defcustom highlight-changes-colours
232 (if (eq (frame-parameter nil 'background-mode) 'light)
233 ;; defaults for light background:
234 '( "magenta" "blue" "darkgreen" "chocolate" "sienna4" "NavyBlue")
235 ;; defaults for dark background:
236 '("yellow" "magenta" "blue" "maroon" "firebrick" "green4" "DarkOrchid"))
237 "*Colours used by `highlight-changes-rotate-faces'.
238The newest rotated change will be displayed in the first element of this list,
239the next older will be in the second element etc.
240
241This list is used if `highlight-changes-face-list' is nil, otherwise that
242variable overrides this list. If you only care about foreground
243colours then use this, if you want fancier faces then set
244`highlight-changes-face-list'."
245 :type '(repeat color)
246 :group 'highlight-changes
247 )
248
249
250;; If you invoke highlight-changes-mode with no argument, should it start in
251;; active or passive mode?
252;;
253(defcustom highlight-changes-initial-state 'active
254 "*What state (active or passive) `highlight-changes' should start in.
255This is used when `highlight-changes' is called with no argument.
256This variable must be set to one of the symbols `active' or `passive'."
257 :type '(choice (const :tag "Active" active)
258 (const :tag "Passive" passive))
259 :group 'highlight-changes
260 )
261
262(defcustom highlight-changes-global-initial-state 'passive
263 "*What state `global-highlight-changes' should start in.
264This is used if `global-highlight-changes' is called with no argument.
265This variable must be set to either `active' or `passive'"
266 :type '(choice (const :tag "Active" active)
267 (const :tag "Passive" passive))
268 :group 'highlight-changes
269 )
270
271;; The strings displayed in the mode-line for the minor mode:
272(defcustom highlight-changes-active-string nil
273 "*The string used when Highlight Changes mode is in the active state.
274This should be set to nil if no indication is desired, or to
275a string with a leading space."
276 :type '(choice string
277 (const :tag "None" nil))
278 :group 'highlight-changes
279 )
280
281(defcustom highlight-changes-passive-string " Chg"
282 "*The string used when Highlight Changes mode is in the passive state.
283This should be set to nil if no indication is desired, or to
284a string with a leading space."
285 :type '(choice string
286 (const :tag "None" nil))
287 :group 'highlight-changes
288 )
289
290(defcustom highlight-changes-global-modes t
291 "*Determine whether a buffer is suitable for global Highlight Changes mode.
292
293A function means that function is called: if it returns non-nil the
294buffer is suitable.
295
296A list is a list of modes for which it is suitable, or a list whose
297first element is 'not followed by modes which are not suitable.
298
299t means the buffer is suitable if its name does not begin with ` ' nor
300`*' and the buffer has a filename.
301
302nil means no buffers are suitable for `global-highlight-changes'
303(effectively disabling the mode).
304
305Examples:
306 (c-mode c++-mode)
307means that Highlight Changes mode is turned on for buffers in C and C++
308modes only."
309 :type '(choice
310 (const :tag "all non-special buffers visiting files" t)
311 (set :menu-tag "specific modes" :tag "modes"
312 :value (not)
313 (const :tag "All except these" not)
314 (repeat :tag "Modes" :inline t (symbol :tag "mode")))
315 (function :menu-tag "determined by function"
316 :value buffer-file-name)
317 (const :tag "none" nil)
318 )
319 :group 'highlight-changes
320 )
321
322
323(defvar global-highlight-changes nil)
324
325(defcustom highlight-changes-global-changes-existing-buffers nil
326 "*If non-nil toggling global Highlight Changes mode affects existing buffers.
327Normally, `global-highlight-changes' means affects only new buffers (to be
328created). However, if highlight-changes-global-changes-existing-buffers
329is non-nil then turning on `global-highlight-changes' will turn on
330highlight-changes-mode in suitable buffers and turning the mode off will
331remove it from existing buffers."
332 :type 'boolean
333 :group 'highlight-changes)
334
335(defun hilit-chg-cust-fix-changes-face-list (w wc &optional event)
336 ;; When customization function `highlight-changes-face-list' inserts a new
337 ;; face it uses the default face. We don't want the user to modify this
338 ;; face, so we rename the faces in the list on an insert. The rename is
339 ;; actually done by copying the faces so user-defined faces still remain
340 ;; in the same order.
341 ;; The notifying the parent is needed because without it changes to the
342 ;; faces are saved but not to the actual list itself.
343 (let ((old-list (widget-value w)))
344 (if (member 'default old-list)
345 (let
346 ((p (reverse old-list))
347 (n (length old-list))
348 new-name old-name
349 (new-list nil)
350 )
351 (while p
352 (setq old-name (car p))
353 (setq new-name (intern (format "highlight-changes-face-%d" n)))
354 (if (eq old-name new-name)
355 nil
356 ;; A new face has been inserted: we don't want to modify the
357 ;; default face so copy it. Better, though, (I think) is to
358 ;; make a new face have the same attributes as
359 ;; highlight-changes-face .
360 (if (eq old-name 'default)
361 (copy-face 'highlight-changes-face new-name)
362 (copy-face old-name new-name)
363 ))
364 (setq new-list (append (list new-name) new-list))
365 (setq n (1- n))
366 (setq p (cdr p)))
367 (if (equal new-list (widget-value w))
368 nil ;; (message "notify: no change!")
369 (widget-value-set w new-list)
370 (widget-setup)
371 )
372 )
373 ;; (message "notify: no default here!")
374 ))
375 (let ((parent (widget-get w :parent)))
376 (when parent
377 (widget-apply parent :notify w event)))
378 )
379
380
381(defcustom highlight-changes-face-list nil
382 "*A list of faces used when rotatating changes.
383Normally the variable is initialized to nil and the list is created from
384`highlight-changes-colours' when needed. However, you can set this variable
385to any list of faces. You will have to do this if you want faces which
386don't just differ from `highlight-changes-face' by the foreground colour.
387Otherwise, this list will be constructed when needed from
388`highlight-changes-colours'."
389 :type '(choice
390 (repeat
391 :notify hilit-chg-cust-fix-changes-face-list
392 face )
393 (const :tag "Derive from highlight-changes-colours" nil)
394 )
395 :group 'highlight-changes
396 )
397
398;; ========================================================================
399
400;; These shouldn't be changed!
401
402(defvar highlight-changes-mode nil)
403(defvar hilit-chg-list nil)
404(defvar hilit-chg-string " ??")
405(or (assq 'highlight-changes-mode minor-mode-alist)
406 (setq minor-mode-alist
407 (cons '(highlight-changes-mode hilit-chg-string) minor-mode-alist)
408 ))
409(make-variable-buffer-local 'highlight-changes-mode)
410(make-variable-buffer-local 'hilit-chg-string)
411
412
413
414(eval-and-compile
99f08df4 415 ;; For highlight-compare-with-file
e287d328
RS
416 (defvar ediff-number-of-differences)
417 (autoload 'ediff-setup "ediff")
418 (autoload 'ediff-with-current-buffer "ediff")
419 (autoload 'ediff-really-quit "ediff")
420 (autoload 'ediff-make-fine-diffs "ediff")
421 (autoload 'ediff-get-fine-diff-vector "ediff")
422 (autoload 'ediff-get-difference "ediff")
423 )
424
425
426
427;;; Functions...
428
429(defun hilit-chg-map-changes (func &optional start-position end-position)
430 "Call function FUNC for each region used by Highlight Changes mode."
431 ;; if start-position is nil, (point-min) is used
432 ;; if end-position is nil, (point-max) is used
433 ;; FUNC is called with 3 params: property start stop
434 (let ((start (or start-position (point-min)))
435 (limit (or end-position (point-max)))
436 prop end)
437 (while (and start (< start limit))
438 (setq prop (get-text-property start 'hilit-chg))
439 (setq end (text-property-not-all start limit 'hilit-chg prop))
440 (if prop
441 (funcall func prop start (or end limit)))
442 (setq start end)
443 )))
444
445
446(defun hilit-chg-display-changes (&optional beg end)
447 "Display face information for Highlight Changes mode.
448
449An overlay containing a change face is added, from the information
450in the text property of type change.
451
452This is the opposite of hilit-chg-hide-changes."
453 (hilit-chg-map-changes 'hilit-chg-make-ov beg end))
454
455
456(defun hilit-chg-make-ov (prop start end)
457 ;; for the region make change overlays corresponding to
458 ;; the text property 'hilit-chg
459 (let ((ov (make-overlay start end))
460 face)
461 (or prop
462 (error "hilit-chg-make-ov: prop is nil"))
463 (if (eq prop 'hilit-chg-delete)
464 (setq face 'highlight-changes-delete-face)
465 (setq face (nth 1 (member prop hilit-chg-list))))
466 (if face
467 (progn
468 ;; We must mark the face, that is the purpose of the overlay
469 (overlay-put ov 'face face)
470 ;; I don't think we need to set evaporate since we should
471 ;; be controlling them!
472 (overlay-put ov 'evaporate t)
473 ;; We set the change property so we can tell this is one
474 ;; of our overlays (so we don't delete someone else's).
475 (overlay-put ov 'hilit-chg t)
476 )
477 (error "hilit-chg-make-ov: no face for prop: %s" prop)
478 )
479 ))
480
481(defun hilit-chg-hide-changes (&optional beg end)
482 "Remove face information for Highlight Changes mode.
483
484The overlay containing the face is removed, but the text property
485containing the change information is retained.
486
487This is the opposite of hilit-chg-display-changes."
488 (let ((start (or beg (point-min)))
489 (limit (or end (point-max)))
490 p ov)
491 (setq p (overlays-in start limit))
492 (while p
493 ;; don't delete the overlay if it isn't ours!
494 (if (overlay-get (car p) 'hilit-chg)
495 (delete-overlay (car p)))
496 (setq p (cdr p))
497 )))
498
499(defun hilit-chg-fixup (beg end)
500 "Fix change overlays in region beg .. end.
501
502Ensure the overlays agree with the changes as determined from
503the text properties of type `hilit-chg' ."
504 ;; Remove or alter overlays in region beg..end
505 (let (p ov ov-start ov-end
506 props q)
507 (setq p (overlays-in beg end))
508 ;; temp for debugging:
509 ;; (or (eq highlight-changes-mode 'active)
510 ;; (error "hilit-chg-fixup called but Highlight Changes mode not active"))
511 (while p
512 (setq ov (car p))
513 (setq ov-start (overlay-start ov))
514 (setq ov-end (overlay-end ov))
515 (if (< ov-start beg)
516 (progn
517 (move-overlay ov ov-start beg)
518 (if (> ov-end end)
519 (progn
520 (setq props (overlay-properties ov))
521 (setq ov (make-overlay end ov-end))
522 (while props
523 (overlay-put ov (car props)(car (cdr props)))
524 (setq props (cdr (cdr props))))
525 )
526 )
527 )
528 (if (> ov-end end)
529 (move-overlay ov end ov-end)
530 (delete-overlay ov)
531 ))
532 (setq p (cdr p)))
533 (hilit-chg-display-changes beg end)
534 ))
535
536
537
538
539
540
541;;;###autoload
542(defun highlight-changes-remove-highlight (beg end)
543 "Remove the change face from the region.
544This allows you to manually remove highlighting from uninteresting changes."
545 (interactive "r")
546 (let ((after-change-functions nil))
547 (remove-text-properties beg end '(hilit-chg nil))
548 (hilit-chg-fixup beg end)))
549
550(defun hilit-chg-set-face-on-change (beg end leng-before
551 &optional no-proerty-change)
552 "Record changes and optionally display them in a distinctive face.
553`hilit-chg-set' adds this function to the `after-change-functions' hook."
554 ;;
555 ;; This function is called by the `after-change-functions' hook, which
556 ;; is how we are notified when text is changed.
99f08df4 557 ;; It is also called from `highlight-compare-with-file'.
e287d328
RS
558 ;;
559 ;; We do NOT want to simply do this if this is an undo command, because
560 ;; otherwise an undone change shows up as changed. While the properties
561 ;; are automatically restored by undo, we must fixup the overlay.
562 (save-match-data
563 (let ((beg-decr 1) (end-incr 1)
564 (type 'hilit-chg)
565 old)
566 (if undo-in-progress
567 (if (eq highlight-changes-mode 'active)
568 (hilit-chg-fixup beg end))
569 (if (and (= beg end) (> leng-before 0))
570 ;; deletion
571 (progn
572 ;; The eolp and bolp tests are a kludge! But they prevent
573 ;; rather nasty looking displays when deleting text at the end
574 ;; of line, such as normal corrections as one is typing and
575 ;; immediately makes a corrections, and when deleting first
576 ;; character of a line.
577;;; (if (= leng-before 1)
578;;; (if (eolp)
579;;; (setq beg-decr 0 end-incr 0)
580;;; (if (bolp)
581;;; (setq beg-decr 0))))
582;;; (setq beg (max (- beg beg-decr) (point-min)))
583 (setq end (min (+ end end-incr) (point-max)))
584 (setq type 'hilit-chg-delete))
585 ;; Not a deletion.
586 ;; Most of the time the following is not necessary, but
587 ;; if the current text was marked as a deletion then
588 ;; the old overlay is still in effect, so if we add some
589 ;; text then remove the deletion marking, but set it to
590 ;; changed otherwise its highlighting disappears.
591 (if (eq (get-text-property end 'hilit-chg) 'hilit-chg-delete)
592 (progn
593 (remove-text-properties end (+ end 1) '(hilit-chg nil))
594 (put-text-property end (+ end 1) 'hilit-chg 'hilit-chg)
595 (if (eq highlight-changes-mode 'active)
596 (hilit-chg-fixup beg (+ end 1))))))
597 (unless no-proerty-change
598 (put-text-property beg end 'hilit-chg type))
599 (if (or (eq highlight-changes-mode 'active) no-proerty-change)
600 (hilit-chg-make-ov type beg end))
601 ))))
602
603
604
605
606
607(defun hilit-chg-set (value)
608 "Turn on Highlight Changes mode for this buffer."
609 (setq highlight-changes-mode value)
610 (remove-hook 'after-change-functions 'hilit-chg-set-face-on-change t)
611 (hilit-chg-make-list)
612 (if (eq highlight-changes-mode 'active)
613 (progn
614 (setq hilit-chg-string highlight-changes-active-string)
615 (or buffer-read-only
616 (hilit-chg-display-changes)))
617 ;; mode is passive
618 (setq hilit-chg-string highlight-changes-passive-string)
619 (or buffer-read-only
620 (hilit-chg-hide-changes))
621 )
622 (force-mode-line-update)
623 (make-local-hook 'after-change-functions)
624 (add-hook 'after-change-functions 'hilit-chg-set-face-on-change nil t)
625 )
626
627(defun hilit-chg-clear ()
628 "Remove Highlight Changes mode for this buffer.
629This removes all saved change information."
630 (if buffer-read-only
631 ;; We print the buffer name because this function could be called
632 ;; on many buffers from `global-highlight-changes'.
633 (message "Cannot remove highlighting from read-only mode buffer %s"
634 (buffer-name))
635 (remove-hook 'after-change-functions 'hilit-chg-set-face-on-change t)
636 (let ((after-change-functions nil))
637 (hilit-chg-hide-changes)
638 (hilit-chg-map-changes
639 '(lambda (prop start stop)
640 (remove-text-properties start stop '(hilit-chg nil))))
641 )
642 (setq highlight-changes-mode nil)
643 (force-mode-line-update)
644 ;; If we type: C-u -1 M-x highlight-changes-mode
645 ;; we want to turn it off, but hilit-chg-post-command-hook
646 ;; runs and that turns it back on!
647 (remove-hook 'post-command-hook 'hilit-chg-post-command-hook)
648 ))
649
650;;;###autoload
651(defun highlight-changes-mode (&optional arg)
652 "Toggle (or initially set) Highlight Changes mode.
653
654Without an argument,
655 if Highlight Changes mode is not enabled, then enable it (to either active
656 or passive as determined by variable highlight-changes-initial-state);
657 otherwise, toggle between active and passive states.
658
659With an argument,
660 if just C-u or a positive argument, set state to active;
661 with a zero argument, set state to passive;
662 with a negative argument, disable Highlight Changes mode completely.
663
664Active state - means changes are shown in a distinctive face.
665Passive state - means changes are kept and new ones recorded but are
666 not displayed in a different face.
667
668Functions:
669\\[highlight-changes-next-change] - move point to beginning of next change
670\\[highlight-changes-previous-change] - move to beginning of previous change
99f08df4
KH
671\\[highlight-compare-with-file] - mark text as changed by comparing this
672 buffer with the contents of a file
e287d328
RS
673\\[highlight-changes-remove-highlight] - remove the change face from the region
674\\[highlight-changes-rotate-faces] - rotate different \"ages\" of changes \
675through
676 various faces.
677
678
679Hook variables:
680highlight-changes-enable-hook - when Highlight Changes mode enabled.
681highlight-changes-toggle-hook - when entering active or passive state
682highlight-changes-disable-hook - when turning off Highlight Changes mode.
683"
684 (interactive "P")
685 (if window-system
686 (let ((was-on highlight-changes-mode)
687 (new-highlight-changes-mode
688 (cond
689 ((null arg)
690 ;; no arg => toggle (or set to active initially)
691 (if highlight-changes-mode
692 (if (eq highlight-changes-mode 'active) 'passive 'active)
693 highlight-changes-initial-state))
694 ;; an argument is given
695 ((eq arg 'active)
696 'active)
697 ((eq arg 'passive)
698 'passive)
699 ((> (prefix-numeric-value arg) 0)
700 'active)
701 ((< (prefix-numeric-value arg) 0)
702 nil)
703 (t
704 'passive)
705 )))
706 (if new-highlight-changes-mode
707 ;; mode is turned on -- but may be passive
708 (progn
709 (hilit-chg-set new-highlight-changes-mode)
710 (or was-on
711 ;; run highlight-changes-enable-hook once
712 (run-hooks 'highlight-changes-enable-hook))
713 (run-hooks 'highlight-changes-toggle-hook))
714 ;; mode is turned off
715 (run-hooks 'highlight-changes-disable-hook)
716 (hilit-chg-clear))
717 )
718 (message "Highlight Changes mode only works when using a window system"))
719 )
720
721
722
723;;;###autoload
724(defun highlight-changes-next-change ()
725 "Move to the beginning of the next change, if in Highlight Changes mode."
726 (interactive)
727 (if highlight-changes-mode
728 (let ((start (point))
729 prop)
730 (setq prop (get-text-property (point) 'hilit-chg))
731 (if prop
732 ;; we are in a change
733 (setq start (next-single-property-change (point) 'hilit-chg)))
734 (if start
735 (setq start (next-single-property-change start 'hilit-chg)))
736 (if start
737 (goto-char start)
738 (message "no next change")))
739 (message "This buffer is not in Highlight Changes mode.")))
740
741
742;;;###autoload
743(defun highlight-changes-previous-change ()
744 "Move to the beginning of the previous change, if in Highlight Changes mode."
745 (interactive)
746 (if highlight-changes-mode
747 (let ( (start (point)) (prop nil) )
748 (or (bobp)
749 (setq prop (get-text-property (1- (point)) 'hilit-chg)))
750 (if prop
751 ;; we are in a change
752 (setq start (previous-single-property-change (point) 'hilit-chg)))
753 (if start
754 (setq start (previous-single-property-change start 'hilit-chg)))
755 ;; special handling for the case where (point-min) is a change
756 (if start
757 (setq start (or (previous-single-property-change start 'hilit-chg)
758 (if (get-text-property (point-min) 'hilit-chg)
759 (point-min)))))
760 (if start
761 (goto-char start)
762 (message "no previous change")))
763 (message "This buffer is not in Highlight Changes mode.")))
764
765
766;; ========================================================================
767
768
769(defun hilit-chg-make-list (&optional force)
770 "Construct hilit-chg-list and highlight-changes-face-list."
771 ;; Constructs highlight-changes-face-list if necessary,
772 ;; and hilit-chg-list always:
773 ;; Maybe this should always be called when rotating a face
774 ;; so we pick up any changes?
775 (if (or (null highlight-changes-face-list) ; Don't do it if it
776 force) ; already exists unless FORCE non-nil.
777 (let ((p highlight-changes-colours)
778 (n 1) name)
779 (setq highlight-changes-face-list nil)
780 (while p
781 (setq name (intern (format "highlight-changes-face-%d" n)))
782 (copy-face 'highlight-changes-face name)
783 (set-face-foreground name (car p))
784 (setq highlight-changes-face-list
785 (append highlight-changes-face-list (list name)))
786 (setq p (cdr p))
787 (setq n (1+ n)))))
788 (setq hilit-chg-list (list 'hilit-chg 'highlight-changes-face))
789 (let ((p highlight-changes-face-list)
790 (n 1)
791 last-category last-face)
792 (while p
793 (setq last-category (intern (format "change-%d" n)))
794 ;; (setq last-face (intern (format "highlight-changes-face-%d" n)))
795 (setq last-face (car p))
796 (setq hilit-chg-list
797 (append hilit-chg-list
798 (list last-category last-face)))
799 (setq p (cdr p))
800 (setq n (1+ n)))
801 (setq hilit-chg-list
802 (append hilit-chg-list
803 (list last-category last-face)))
804 ))
805
806
807(defun hilit-chg-bump-change (prop start end)
808 "Increment (age) the Highlight Changes mode text property of type change."
809 (let ( new-prop )
810 (if (eq prop 'hilit-chg-delete)
811 (setq new-prop (nth 2 hilit-chg-list))
812 (setq new-prop (nth 2 (member prop hilit-chg-list)))
813 )
814 (if prop
815 (put-text-property start end 'hilit-chg new-prop)
816 (message "%d-%d unknown property %s not changed" start end prop)
817 )
818 ))
819
820;;;###autoload
821(defun highlight-changes-rotate-faces ()
822 "Rotate the faces used by Highlight Changes mode.
823
824Current changes will be display in the face described by the first element
825of highlight-changes-face-list, those (older) changes will be shown in the
826face described by the second element, and so on. Very old changes remain
827shown in the last face in the list.
828
829You can automatically rotate colours when the buffer is saved
830by adding this to local-write-file-hooks, by evaling (in the
831buffer to be saved):
832 (add-hook 'local-write-file-hooks 'highlight-changes-rotate-faces)
833"
834 (interactive)
835 ;; If not in active mode do nothing but don't complain because this
836 ;; may be bound to a hook.
837 (if (eq highlight-changes-mode 'active)
838 (let ((after-change-functions nil))
839 ;; ensure hilit-chg-list is made and up to date
840 (hilit-chg-make-list)
841 ;; remove our existing overlays
842 (hilit-chg-hide-changes)
843 ;; for each change text property, increment it
844 (hilit-chg-map-changes 'hilit-chg-bump-change)
845 ;; and display them all if active
846 (if (eq highlight-changes-mode 'active)
847 (hilit-chg-display-changes))
848 ))
849 ;; This always returns nil so it is safe to use in
850 ;; local-write-file-hook
851 nil)
852
853
854;; ========================================================================
855;; Comparing with an existing file.
856;; This uses ediff to find the differences.
857
858;;;###autoload
b32e3ef8
KH
859(defun highlight-compare-with-file (file-b)
860 "Compare this buffer with a file, and highlight differences.
e287d328
RS
861
862The current buffer must be an unmodified buffer visiting a file,
863and not in read-only mode.
864
865If the backup filename exists, it is used as the default
866when called interactively.
867
868If a buffer is visiting the file being compared against, it also will
869have its differences highlighted. Otherwise, the file is read in
870temporarily but the buffer is deleted.
871
872If a buffer is read-only, differences will be highlighted but no property
873changes made, so \\[highlight-changes-next-change] and
874\\[highlight-changes-previous-change] will not work."
875 (interactive (list
876 (read-file-name
877 "File to compare with? " ;; prompt
878 "" ;; directory
879 nil ;; default
880 'yes ;; must exist
881 (let ((f (make-backup-file-name
882 (or (buffer-file-name (current-buffer))
883 (error "no file for this buffer")))))
884 (if (file-exists-p f) f ""))
885 )))
886
887 (let* ((buf-a (current-buffer))
888 (buf-a-read-only buffer-read-only)
889 (orig-pos (point))
890 (file-a (buffer-file-name))
891 (existing-buf (get-file-buffer file-b))
892 (buf-b (or existing-buf
893 (find-file-noselect file-b)))
894 (buf-b-read-only (with-current-buffer buf-b buffer-read-only))
895 xy xx yy p q
896 a-start a-end len-a
897 b-start b-end len-b
898 )
899
900 ;; We use the fact that the buffer is not marked modified at the
901 ;; end where we clear its modified status
902 (if (buffer-modified-p buf-a)
903 (if (y-or-n-p (format "OK to save %s? " file-a))
904 (save-buffer buf-a)
905 (error "Buffer must be saved before comparing with a file.")))
906 (if (and existing-buf (buffer-modified-p buf-b))
907 (if (y-or-n-p (format "OK to save %s? " file-b))
908 (save-buffer buf-b)
909 (error "Cannot compare with a file in an unsaved buffer.")))
910 (highlight-changes-mode 'active)
911 (if existing-buf (with-current-buffer buf-b
912 (highlight-changes-mode 'active)))
913 (save-window-excursion
914 (setq xy (hilit-chg-get-diff-info buf-a file-a buf-b file-b)))
915 (setq xx (car xy))
916 (setq p xx)
917 (setq yy (car (cdr xy)))
918 (setq q yy)
919 (hilit-chg-make-list)
920 (while p
921 (setq a-start (nth 0 (car p)))
922 (setq a-end (nth 1 (car p)))
923 (setq b-start (nth 0 (car q)))
924 (setq b-end (nth 1 (car q)))
925 (setq len-a (- a-end a-start))
926 (setq len-b (- b-end b-start))
927 (set-buffer buf-a)
928 (hilit-chg-set-face-on-change a-start a-end len-b buf-a-read-only)
929 (set-buffer-modified-p nil)
930 (goto-char orig-pos)
931 (if existing-buf
932 (with-current-buffer buf-b
933 (hilit-chg-set-face-on-change b-start b-end len-a
934 buf-b-read-only )
935 ))
936 (setq p (cdr p))
937 (setq q (cdr q))
938 )
939 (if existing-buf
940 (set-buffer-modified-p nil)
941 (kill-buffer buf-b))
942 ))
943
944
945(defun hilit-chg-get-diff-info (buf-a file-a buf-b file-b)
946 (let ((e nil) x y) ;; e is set by function hilit-chg-get-diff-list-hk
947 (ediff-setup buf-a file-a buf-b file-b
948 nil nil ; buf-c file-C
949 'hilit-chg-get-diff-list-hk
950 (list (cons 'ediff-job-name 'something))
951 )
952 (ediff-with-current-buffer e (ediff-really-quit nil))
953 (list x y)))
954
955
956(defun hilit-chg-get-diff-list-hk ()
957 ;; x and y are dynamically bound by hilit-chg-get-diff-info
958 ;; which calls this function as a hook
959 (defvar x) ;; placate the byte-compiler
960 (defvar y)
961 (setq e (current-buffer))
962 (let ((n 0) extent p va vb a b)
963 (setq x nil y nil) ;; x and y are bound by hilit-chg-get-diff-info
964 (while (< n ediff-number-of-differences)
965 (ediff-make-fine-diffs n)
966 (setq va (ediff-get-fine-diff-vector n 'A))
967 ;; va is a vector if there are fine differences
968 (if va
969 (setq a (append va nil))
970 ;; if not, get the unrefined difference
971 (setq va (ediff-get-difference n 'A))
972 (setq a (list (elt va 0)))
973 )
974 ;; a list a list
975 (setq p a)
976 (while p
977 (setq extent (list (overlay-start (car p))
978 (overlay-end (car p))))
979 (setq p (cdr p))
980 (setq x (append x (list extent) ))
981 );; while p
982 ;;
983 (setq vb (ediff-get-fine-diff-vector n 'B))
984 ;; vb is a vector
985 (if vb
986 (setq b (append vb nil))
987 ;; if not, get the unrefined difference
988 (setq vb (ediff-get-difference n 'B))
989 (setq b (list (elt vb 0)))
990 )
991 ;; b list a list
992 (setq p b)
993 (while p
994 (setq extent (list (overlay-start (car p))
995 (overlay-end (car p))))
996 (setq p (cdr p))
997 (setq y (append y (list extent) ))
998 );; while p
999 ;;
1000 (setq n (1+ n))
1001 );; while
1002 ;; ediff-quit doesn't work here.
1003 ;; No point in returning a value, since this is a hook function.
1004 ))
1005
1006;; ======================= automatic stuff ==============
1007
1008;; Global Highlight Changes mode is modelled after Global Font-lock mode.
1009;; Three hooks are used to gain control. When Global Changes Mode is
1010;; enabled, `find-file-hooks' and `change-major-mode-hook' are set.
1011;; `find-file-hooks' is called when visiting a file, the new mode is
1012;; known at this time.
1013;; `change-major-mode-hook' is called when a buffer is changing mode.
1014;; This could be because of finding a file in which case
1015;; `find-file-hooks' has already been called and has done its work.
1016;; However, it also catches the case where a new mode is being set by
1017;; the user. However, it is called from `kill-all-variables' and at
1018;; this time the mode is the old mode, which is not what we want.
1019;; So, our function temporarily sets `post-command-hook' which will
1020;; be called after the buffer has been completely set up (with the new
1021;; mode). It then removes the `post-command-hook'.
1022;; One other wrinkle - every M-x command runs the `change-major-mode-hook'
1023;; so we ignore this by examining the buffer name.
1024
1025
1026(defun hilit-chg-major-mode-hook ()
1027 (add-hook 'post-command-hook 'hilit-chg-post-command-hook)
1028 )
1029
1030(defun hilit-chg-post-command-hook ()
1031 ;; This is called after changing a major mode, but also after each
1032 ;; M-x command, in which case the current buffer is a minibuffer.
1033 ;; In that case, do not act on it here, but don't turn it off
1034 ;; either, we will get called here again soon-after.
1035 ;; Also, don't enable it for other special buffers.
1036 (if (string-match "^[ *]" (buffer-name))
1037 nil ;; (message "ignoring this post-command-hook")
1038 (remove-hook 'post-command-hook 'hilit-chg-post-command-hook)
1039 ;; The following check isn't necessary, since
1040 ;; hilit-chg-turn-on-maybe makes this check too.
1041 (or highlight-changes-mode ;; don't turn it on if it already is
1042 (hilit-chg-turn-on-maybe highlight-changes-global-initial-state))
1043 ))
1044
1045(defun hilit-chg-check-global ()
1046 ;; This is called from the find file hook.
1047 (hilit-chg-turn-on-maybe highlight-changes-global-initial-state))
1048
1049
1050
1051;;;###autoload
1052(defun global-highlight-changes (&optional arg)
1053 "Turn on or off global Highlight Changes mode.
1054
1055When called interactively:
1056- if no prefix, toggle global Highlight Changes mode on or off
1057- if called with a positive prefix (or just C-u) turn it on in active mode
1058- if called with a zero prefix turn it on in passive mode
1059- if called with a negative prefix turn it off
1060
1061When called from a program:
1062- if ARG is nil or omitted, turn it off
1063- if ARG is 'active, turn it on in active mode
1064- if ARG is 'passive, turn it on in passive mode
1065- otherwise just turn it on
1066
1067When global Highlight Changes mode is enabled, Highlight Changes mode is turned
1068on for future \"suitable\" buffers (and for \"suitable\" existing buffers if
1069variable `highlight-changes-global-changes-existing-buffers' is non-nil).
1070\"Suitablity\" is determined by variable `highlight-changes-global-modes'."
1071
1072 (interactive
1073 (list
1074 (cond
1075 ((null current-prefix-arg)
1076 ;; no arg => toggle it on/off
1077 (setq global-highlight-changes (not global-highlight-changes)))
1078 ;; positive interactive arg - turn it on as active
1079 ((> (prefix-numeric-value current-prefix-arg) 0)
1080 (setq global-highlight-changes t)
1081 'active)
1082 ;; zero interactive arg - turn it on as passive
1083 ((= (prefix-numeric-value current-prefix-arg) 0)
1084 (setq global-highlight-changes t)
1085 'passive)
1086 ;; negative interactive arg - turn it off
1087 (t
1088 (setq global-highlight-changes nil)
1089 nil))))
1090
1091 (if arg
1092 (progn
1093 (if (eq arg 'active)
1094 (setq highlight-changes-global-initial-state 'active)
1095 (if (eq arg 'passive)
1096 (setq highlight-changes-global-initial-state 'passive)))
1097 (setq global-highlight-changes t)
1098 (message "turning ON Global Highlight Changes mode in %s state"
1099 highlight-changes-global-initial-state)
1100 (add-hook 'hilit-chg-major-mode-hook 'hilit-chg-major-mode-hook)
1101 (add-hook 'find-file-hooks 'hilit-chg-check-global)
1102 (if highlight-changes-global-changes-existing-buffers
1103 (hilit-chg-update-all-buffers
1104 highlight-changes-global-initial-state))
1105 )
1106 (message "turning OFF global Highlight Changes mode")
1107 (remove-hook 'hilit-chg-major-mode-hook 'hilit-chg-major-mode-hook)
1108 (remove-hook 'find-file-hooks 'hilit-chg-check-global)
1109 (remove-hook 'post-command-hook
1110 'hilit-chg-post-command-hook)
1111 (remove-hook 'find-file-hooks 'hilit-chg-check-global)
1112 (if highlight-changes-global-changes-existing-buffers
1113 (hilit-chg-update-all-buffers nil))
1114 )
1115 )
1116
1117
1118
1119
1120
1121(defun hilit-chg-turn-on-maybe (value)
1122 "Turn on Highlight Changes mode if it is appropriate for this buffer.
1123
1124A buffer is appropriate for Highlight Changes mode if all these are true:
1125- the buffer is not a special buffer (one whose name begins with
1126 `*' or ` ')
1127- the buffer's mode is suitable as per variable highlight-changes-global-modes
1128- Highlight Changes mode is not already on for this buffer.
1129
1130This function is called from hilit-chg-update-all-buffers
1131from `global-highlight-changes' when turning on global Highlight Changes mode.
1132"
1133 (or highlight-changes-mode ; do nothing if already on
1134 (if
1135 (cond
1136 ((null highlight-changes-global-modes)
1137 nil)
1138 ((functionp highlight-changes-global-modes)
1139 (funcall highlight-changes-global-modes))
1140 ((listp highlight-changes-global-modes)
1141 (if (eq (car-safe highlight-changes-global-modes) 'not)
1142 (not (memq major-mode (cdr highlight-changes-global-modes)))
1143 (memq major-mode highlight-changes-global-modes)))
1144 (t
1145 (and
1146 (not (string-match "^[ *]" (buffer-name)))
1147 (buffer-file-name))
1148 ))
1149 (progn
1150 (hilit-chg-set value)
1151 (run-hooks 'highlight-changes-enable-hook)))
1152 ))
1153
1154
1155(defun hilit-chg-turn-off-maybe ()
1156 (if highlight-changes-mode
1157 (progn
1158 (run-hooks 'highlight-changes-disable-hook)
1159 (hilit-chg-clear))))
1160
1161
1162
1163(defun hilit-chg-update-all-buffers (value)
1164 (mapcar
1165 (function (lambda (buffer)
1166 (with-current-buffer buffer
1167 (if value
1168 (hilit-chg-turn-on-maybe value)
1169 (hilit-chg-turn-off-maybe))
1170 )))
1171 (buffer-list)))
1172
1173;; ===================== debug ==================
1174;; For debug & test use:
1175;;
1176;; (defun hilit-chg-debug-show (&optional beg end)
1177;; (interactive)
1178;; (message "--- hilit-chg-debug-show ---")
1179;; (hilit-chg-map-changes '(lambda (prop start end)
1180;; (message "%d-%d: %s" start end prop)
1181;; )
1182;; beg end
1183;; ))
1184;;
1185;; ================== end of debug ===============
1186
1187
1188(provide 'hilit-chg)
e287d328 1189
fdbd749a 1190;;; hilit-chg.el ends here