Don't quote lambda expressions with `quote'.
[bpt/emacs.git] / lisp / hilit-chg.el
CommitLineData
e287d328
RS
1;;; hilit-chg.el --- minor mode displaying buffer changes with special face
2
73b0cd50 3;; Copyright (C) 1998, 2000-2011 Free Software Foundation, Inc.
e287d328 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
eb3fa2cf 10;; GNU Emacs is free software: you can redistribute it and/or modify
e287d328 11;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
e287d328 14
2be7dabc 15;; GNU Emacs is distributed in the hope that it will be useful,
e287d328
RS
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
eb3fa2cf 21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
e287d328 22
e287d328
RS
23;;; Commentary:
24
25;; A minor mode: "Highlight Changes mode".
e287d328 26
7c655cf6
SM
27;; When Highlight Changes mode is enabled changes to the buffer are
28;; recorded with a text property. Normally these ranges of text are
29;; displayed in a distinctive face. However, sometimes it is
30;; desirable to temporarily not see these changes. Instead of
31;; disabling Highlight Changes mode (which would remove the text property)
32;; use the command highlight-changes-visible-mode.
33
34;; Two faces are supported: one for changed or inserted text and
35;; another for the first character after text has been deleted.
36
37;; When Highlight Changes mode is on (even if changes are not visible)
38;; you can go to the next or previous change with
39;; `highlight-changes-next-change' or `highlight-changes-previous-change'.
40
41;; Command highlight-compare-with-file shows changes in this file
42;; compared with another file (by default the previous version of the
43;; file).
e287d328 44;;
7c655cf6
SM
45;; The command highlight-compare-buffers compares two buffers by
46;; highlighting their differences.
47
e287d328 48;; You can "age" different sets of changes by using
3ec30bcb 49;; `highlight-changes-rotate-faces'. This rotates through a series
e287d328 50;; of different faces, so you can distinguish "new" changes from "older"
00e25b96 51;; changes. You can customize these "rotated" faces in two ways. You can
e287d328
RS
52;; either explicitly define each face by customizing
53;; `highlight-changes-face-list'. If, however, the faces differ from
7c655cf6 54;; `highlight-changes-face' only in the foreground color, you can simply set
b1412131 55;; `highlight-changes-colors'. If `highlight-changes-face-list' is nil when
e287d328 56;; the faces are required they will be constructed from
b1412131 57;; `highlight-changes-colors'.
7c655cf6
SM
58
59;; You can automatically rotate faces when the buffer is saved;
60;; see function `highlight-changes-rotate-faces' for how to do this.
61
7ebafc09
JB
62;; The hook `highlight-changes-mode-hook' is called when
63;; Highlight Changes mode is turned on or off.
64;; When it called, variable `highlight-changes-mode' has been updated
65;; to the new value.
71296446 66;;
7ebafc09
JB
67;; Example usage:
68;; (defun my-highlight-changes-mode-hook ()
69;; (if highlight-changes-mode
70;; (add-hook 'write-file-functions 'highlight-changes-rotate-faces nil t)
71;; (remove-hook 'write-file-functions 'highlight-changes-rotate-faces t)
72;; ))
e287d328
RS
73
74
7c655cf6 75;; Automatically enabling Highlight Changes mode
e287d328
RS
76;;
77
78;; Normally, Highlight Changes mode is turned on explicitly in a buffer.
79;;
80;; If you prefer to have it automatically invoked you can do it as
81;; follows.
7c655cf6 82
e287d328 83;; 1. Most modes have a major-hook, typically called MODE-hook. You
71296446 84;; can use `add-hook' to call `highlight-changes-mode'.
7c655cf6 85
e287d328
RS
86;; Example:
87;; (add-hook 'c-mode-hook 'highlight-changes-mode)
7c655cf6 88
e287d328
RS
89;; However, this cannot be done for Fundamental mode for there is no
90;; such hook.
7c655cf6
SM
91
92;; 2. You can use the function `global-highlight-changes-mode'
e287d328
RS
93;;
94;; This function, which is fashioned after the way `global-font-lock' works,
95;; toggles on or off global Highlight Changes mode. When activated, it turns
3ec30bcb 96;; on Highlight Changes mode in all "suitable" existing buffers and will turn
e287d328 97;; it on in new "suitable" buffers to be created.
7c655cf6 98
e287d328 99;; A buffer's "suitability" is determined by variable
7c655cf6 100;; `highlight-changes-global-modes', as follows. If it is
e287d328
RS
101;; * nil -- then no buffers are suitable;
102;; * a function -- this function is called and the result is used. As
f6ec4635 103;; an example, if the value is `buffer-file-name' then all buffers
e287d328
RS
104;; who are visiting files are suitable, but others (like dired
105;; buffers) are not;
7c655cf6 106;; * a list -- then the buffer is suitable if and only if its mode is in the
f6ec4635 107;; list, except if the first element is `not', in which case the test
e287d328 108;; is reversed (i.e. it is a list of unsuitable modes).
f6ec4635 109;; * Otherwise, the buffer is suitable if its name does not begin with
e287d328 110;; ` ' or `*' and if `buffer-file-name' returns true.
e287d328 111
7c655cf6
SM
112;; To enable it for future sessions put this in your ~/.emacs file:
113;; (global-highlight-changes-mode t)
e287d328
RS
114
115
116;; Possible bindings:
117;; (global-set-key '[C-right] 'highlight-changes-next-change)
118;; (global-set-key '[C-left] 'highlight-changes-previous-change)
119;;
7c655cf6 120;; Other interactive functions (that could be bound if desired):
e287d328 121;; highlight-changes-mode
7c655cf6 122;; highlight-changes-toggle-visibility
e287d328 123;; highlight-changes-remove-highlight
99f08df4 124;; highlight-compare-with-file
f826bf67 125;; highlight-compare-buffers
7c655cf6 126;; highlight-changes-rotate-faces
e287d328
RS
127
128
129;;; Bugs:
130
131;; - the next-change and previous-change functions are too literal;
f6ec4635 132;; they should find the next "real" change, in other words treat
e287d328
RS
133;; consecutive changes as one.
134
135
f6ec4635 136;;; To do (maybe), notes, ...
e287d328
RS
137
138;; - having different faces for deletion and non-deletion: is it
139;; really worth the hassle?
99f08df4
KH
140;; - highlight-compare-with-file should allow RCS files - e.g. nice to be
141;; able to say show changes compared with version 2.1.
e287d328
RS
142
143
144;;; History:
145
f826bf67 146;; R Sharman (rsharman@pobox.com) Feb 1998:
e287d328 147;; - initial release as change-mode.
e287d328 148;; Jari Aalto <jari.aalto@ntc.nokia.com> Mar 1998
71296446 149;; - fixes for byte compile errors
e287d328
RS
150;; - use eval-and-compile for autoload
151;; Marijn Ros <J.M.Ros@fys.ruu.nl> Mar 98
152;; - suggested turning it on by default
e287d328
RS
153;; Eric Ludlam <zappo@gnu.org> Suggested using overlays.
154;; July 98
155;; - global mode and various stuff added
156;; - Changed to use overlays
157;; August 98
3ec30bcb 158;; - renamed to Highlight Changes mode.
f826bf67
EZ
159;; Dec 2003
160;; - Use require for ediff stuff
161;; - Added highlight-compare-buffers
7c655cf6
SM
162;; Mar 2008
163;; - Made highlight-changes-mode like other modes (toggle on/off)
164;; - Added new command highlight-changes-visible-mode to replace the
165;; previous active/passive aspect of highlight-changes-mode.
166;; - Removed highlight-changes-toggle-hook
167;; - Put back eval-and-compile inadvertently dropped
7ebafc09
JB
168;; May 2008
169;; - Removed highlight-changes-disable-hook and highlight-changes-enable-hook
170;; because highlight-changes-mode-hook can do both.
e287d328
RS
171
172;;; Code:
173
e287d328
RS
174(require 'wid-edit)
175
176;; ====================== Customization =======================
177(defgroup highlight-changes nil
178 "Highlight Changes mode."
a7845785 179 :version "20.4"
e287d328
RS
180 :group 'faces)
181
182
183;; Face information: How the changes appear.
184
185;; Defaults for face: red foreground, no change to background,
186;; and underlined if a change is because of a deletion.
3ec30bcb 187;; Note: underlining is helpful in that it shows up changes in white space.
e287d328
RS
188;; However, having it set for non-delete changes can be annoying because all
189;; indentation on inserts gets underlined (which can look pretty ugly!).
190
a01853d7 191(defface highlight-changes
2c470dc3 192 '((((min-colors 88) (class color)) (:foreground "red1"))
ea81d57e 193 (((class color)) (:foreground "red" ))
e287d328
RS
194 (t (:inverse-video t)))
195 "Face used for highlighting changes."
3ec30bcb 196 :group 'highlight-changes)
c4f6e489
GM
197(define-obsolete-face-alias 'highlight-changes-face
198 'highlight-changes "22.1")
e287d328
RS
199
200;; This looks pretty ugly, actually. Maybe the underline should be removed.
a01853d7 201(defface highlight-changes-delete
ea81d57e
DN
202 '((((min-colors 88) (class color)) (:foreground "red1" :underline t))
203 (((class color)) (:foreground "red" :underline t))
e287d328
RS
204 (t (:inverse-video t)))
205 "Face used for highlighting deletions."
3ec30bcb 206 :group 'highlight-changes)
c4f6e489
GM
207(define-obsolete-face-alias 'highlight-changes-delete-face
208 'highlight-changes-delete "22.1")
e287d328
RS
209
210
b1412131 211;; A (not very good) default list of colors to rotate through.
cd6ef82d
GM
212(define-obsolete-variable-alias 'highlight-changes-colours
213 'highlight-changes-colors "22.1")
214
b1412131 215(defcustom highlight-changes-colors
e287d328
RS
216 (if (eq (frame-parameter nil 'background-mode) 'light)
217 ;; defaults for light background:
218 '( "magenta" "blue" "darkgreen" "chocolate" "sienna4" "NavyBlue")
219 ;; defaults for dark background:
220 '("yellow" "magenta" "blue" "maroon" "firebrick" "green4" "DarkOrchid"))
01dcf284 221 "Colors used by `highlight-changes-rotate-faces'.
f6ec4635 222The newest rotated change will be displayed in the first element of this list,
e287d328
RS
223the next older will be in the second element etc.
224
f6ec4635
JB
225This list is used if `highlight-changes-face-list' is nil, otherwise that
226variable overrides this list. If you only care about foreground
85fcb671 227colors then use this, if you want fancier faces then set
e287d328 228`highlight-changes-face-list'."
3ec30bcb
GM
229 :type '(repeat color)
230 :group 'highlight-changes)
71296446 231
7c655cf6
SM
232;; When you invoke highlight-changes-mode, should highlight-changes-visible-mode
233;; be on or off?
234
235(define-obsolete-variable-alias 'highlight-changes-initial-state
fb9a90ee 236 'highlight-changes-visibility-initial-state "23.1")
e287d328 237
7c655cf6 238(defcustom highlight-changes-visibility-initial-state t
f3b21763 239 "Controls whether changes are initially visible in Highlight Changes mode.
7c655cf6 240
f3b21763 241This controls the initial value of `highlight-changes-visible-mode'.
7c655cf6 242When a buffer is in Highlight Changes mode the function
f3b21763 243`highlight-changes-visible-mode' is used to toggle the mode on or off."
7c655cf6 244 :type 'boolean
3ec30bcb 245 :group 'highlight-changes)
e287d328 246
7c655cf6
SM
247;; highlight-changes-global-initial-state has been removed
248
249
250
251;; These are the strings displayed in the mode-line for the minor mode:
cd6ef82d
GM
252(define-obsolete-variable-alias 'highlight-changes-active-string
253 'highlight-changes-visible-string "23.1")
7c655cf6
SM
254
255(defcustom highlight-changes-visible-string " +Chg"
256 "The string used when in Highlight Changes mode and changes are visible.
f6ec4635 257This should be set to nil if no indication is desired, or to
e287d328
RS
258a string with a leading space."
259 :type '(choice string
260 (const :tag "None" nil))
3ec30bcb 261 :group 'highlight-changes)
e287d328 262
cd6ef82d
GM
263(define-obsolete-variable-alias 'highlight-changes-passive-string
264 'highlight-changes-invisible-string "23.1")
7c655cf6
SM
265
266(defcustom highlight-changes-invisible-string " -Chg"
267 "The string used when in Highlight Changes mode and changes are hidden.
f6ec4635 268This should be set to nil if no indication is desired, or to
e287d328
RS
269a string with a leading space."
270 :type '(choice string
271 (const :tag "None" nil))
3ec30bcb 272 :group 'highlight-changes)
e287d328
RS
273
274(defcustom highlight-changes-global-modes t
01dcf284 275 "Determine whether a buffer is suitable for global Highlight Changes mode.
e287d328 276
e711842f
RS
277A function means call that function to decide: if it returns non-nil,
278the buffer is suitable.
e287d328 279
e711842f
RS
280A list means the elements are major modes suitable for Highlight
281Changes mode, or a list whose first element is `not' followed by major
282modes which are not suitable.
e287d328 283
2c470dc3
JB
284A value of t means the buffer is suitable if it is visiting a file and
285its name does not begin with ` ' or `*'.
e287d328 286
7c655cf6 287A value of nil means no buffers are suitable for `global-highlight-changes-mode'
852a8571 288\(effectively disabling the mode).
e287d328 289
ea56cdf1 290Example:
7c655cf6 291 (c-mode c++-mode)
e287d328
RS
292means that Highlight Changes mode is turned on for buffers in C and C++
293modes only."
71296446 294 :type '(choice
e287d328
RS
295 (const :tag "all non-special buffers visiting files" t)
296 (set :menu-tag "specific modes" :tag "modes"
297 :value (not)
298 (const :tag "All except these" not)
299 (repeat :tag "Modes" :inline t (symbol :tag "mode")))
300 (function :menu-tag "determined by function"
301 :value buffer-file-name)
302 (const :tag "none" nil)
303 )
3ec30bcb 304 :group 'highlight-changes)
e287d328 305
e287d328 306(defcustom highlight-changes-global-changes-existing-buffers nil
01dcf284 307 "If non-nil, toggling global Highlight Changes mode affects existing buffers.
3ec30bcb
GM
308Normally, `global-highlight-changes' affects only new buffers (to be
309created). However, if `highlight-changes-global-changes-existing-buffers'
310is non-nil, then turning on `global-highlight-changes' will turn on
311Highlight Changes mode in suitable buffers, and turning the mode off will
e287d328
RS
312remove it from existing buffers."
313 :type 'boolean
314 :group 'highlight-changes)
315
7c655cf6
SM
316;; These are for internal use.
317
318(defvar hilit-chg-list nil)
319(defvar hilit-chg-string " ??")
320
321(make-variable-buffer-local 'hilit-chg-string)
322
323
324
325;;; Functions...
326
327;;;###autoload
328(define-minor-mode highlight-changes-mode
329 "Toggle Highlight Changes mode.
330
331With ARG, turn Highlight Changes mode on if and only if arg is positive.
332
333In Highlight Changes mode changes are recorded with a text property.
334Normally they are displayed in a distinctive face, but command
335\\[highlight-changes-visible-mode] can be used to toggles this
336on and off.
337
338Other functions for buffers in this mode include:
339\\[highlight-changes-next-change] - move point to beginning of next change
340\\[highlight-changes-previous-change] - move to beginning of previous change
341\\[highlight-changes-remove-highlight] - remove the change face from the region
342\\[highlight-changes-rotate-faces] - rotate different \"ages\" of changes
343through various faces.
344\\[highlight-compare-with-file] - mark text as changed by comparing this
345buffer with the contents of a file
7ebafc09 346\\[highlight-compare-buffers] highlights differences between two buffers."
7c655cf6
SM
347 nil ;; init-value
348 hilit-chg-string ;; lighter
349 nil ;; keymap
350 (if (or (display-color-p)
351 (and (fboundp 'x-display-grayscale-p) (x-display-grayscale-p)))
352 (progn
353 (if (and (eq this-command 'global-highlight-changes-mode)
354 (not highlight-changes-global-changes-existing-buffers))
355 ;; The global mode has toggled the value of the mode variable,
356 ;; but not other changes have been mode, so we are safe
357 ;; to retoggle it.
358 (setq highlight-changes-mode (not highlight-changes-mode)))
359 (if highlight-changes-mode
360 ;; it is being turned on
7c655cf6
SM
361 (hilit-chg-set)
362 ;; mode is turned off
363 (hilit-chg-clear)))
364 (message "Highlight Changes mode requires color or grayscale display")))
365
366
367;;;###autoload
368(define-minor-mode highlight-changes-visible-mode
369 "Toggle visiblility of changes when buffer is in Highlight Changes mode.
370
371This mode only has an effect when Highlight Changes mode is on.
372It allows toggling between whether or not the changed text is displayed
373in a distinctive face.
374
375The default value can be customized with variable
376`highlight-changes-visibility-initial-state'
377
378This command does not itself set highlight-changes mode."
379
380 t ;; init-value
381 nil ;; lighter
382 nil ;; keymap
383
384 (hilit-chg-update)
385 )
386
387
06b60517 388(defun hilit-chg-cust-fix-changes-face-list (w _wc &optional event)
e287d328 389 ;; When customization function `highlight-changes-face-list' inserts a new
f6ec4635
JB
390 ;; face it uses the default face. We don't want the user to modify this
391 ;; face, so we rename the faces in the list on an insert. The rename is
e287d328
RS
392 ;; actually done by copying the faces so user-defined faces still remain
393 ;; in the same order.
394 ;; The notifying the parent is needed because without it changes to the
395 ;; faces are saved but not to the actual list itself.
396 (let ((old-list (widget-value w)))
397 (if (member 'default old-list)
398 (let
399 ((p (reverse old-list))
400 (n (length old-list))
401 new-name old-name
402 (new-list nil)
403 )
404 (while p
405 (setq old-name (car p))
a01853d7 406 (setq new-name (intern (format "highlight-changes-%d" n)))
e287d328
RS
407 (if (eq old-name new-name)
408 nil
409 ;; A new face has been inserted: we don't want to modify the
f6ec4635 410 ;; default face so copy it. Better, though, (I think) is to
e287d328 411 ;; make a new face have the same attributes as
a01853d7 412 ;; the `highlight-changes' face.
e287d328 413 (if (eq old-name 'default)
a01853d7 414 (copy-face 'highlight-changes new-name)
e287d328
RS
415 (copy-face old-name new-name)
416 ))
ea56cdf1 417 (setq new-list (append (list new-name) new-list))
e287d328
RS
418 (setq n (1- n))
419 (setq p (cdr p)))
420 (if (equal new-list (widget-value w))
421 nil ;; (message "notify: no change!")
422 (widget-value-set w new-list)
423 (widget-setup)
424 )
425 )
426 ;; (message "notify: no default here!")
427 ))
428 (let ((parent (widget-get w :parent)))
429 (when parent
3ec30bcb 430 (widget-apply parent :notify w event))))
e287d328
RS
431
432
433(defcustom highlight-changes-face-list nil
01dcf284 434 "A list of faces used when rotating changes.
e287d328 435Normally the variable is initialized to nil and the list is created from
b1412131 436`highlight-changes-colors' when needed. However, you can set this variable
e287d328 437to any list of faces. You will have to do this if you want faces which
85fcb671 438don't just differ from the `highlight-changes' face by the foreground color.
e287d328 439Otherwise, this list will be constructed when needed from
b1412131 440`highlight-changes-colors'."
e287d328 441 :type '(choice
71296446 442 (repeat
e287d328
RS
443 :notify hilit-chg-cust-fix-changes-face-list
444 face )
b1412131 445 (const :tag "Derive from highlight-changes-colors" nil)
e287d328 446 )
3ec30bcb 447 :group 'highlight-changes)
e287d328 448
e287d328 449
f3b21763 450(defun hilit-chg-map-changes (func &optional start-position end-position)
7c655cf6
SM
451 "Call function FUNC for each region used by Highlight Changes mode.
452If START-POSITION is nil, (point-min) is used.
453If END-POSITION is nil, (point-max) is used.
454FUNC is called with 3 params: PROPERTY START STOP."
e287d328
RS
455 (let ((start (or start-position (point-min)))
456 (limit (or end-position (point-max)))
457 prop end)
458 (while (and start (< start limit))
459 (setq prop (get-text-property start 'hilit-chg))
460 (setq end (text-property-not-all start limit 'hilit-chg prop))
461 (if prop
462 (funcall func prop start (or end limit)))
3ec30bcb 463 (setq start end))))
e287d328
RS
464
465
466(defun hilit-chg-display-changes (&optional beg end)
467 "Display face information for Highlight Changes mode.
468
7c655cf6
SM
469An overlay from BEG to END containing a change face is added from the
470information in the text property of type `hilit-chg'.
e287d328 471
3ec30bcb 472This is the opposite of `hilit-chg-hide-changes'."
e287d328
RS
473 (hilit-chg-map-changes 'hilit-chg-make-ov beg end))
474
475
476(defun hilit-chg-make-ov (prop start end)
f87d9934
RS
477 (or prop
478 (error "hilit-chg-make-ov: prop is nil"))
7c655cf6
SM
479 ;; For the region create overlays with a distincive face
480 ;; and the text property 'hilit-chg.
e287d328 481 (let ((ov (make-overlay start end))
01dcf284
SM
482 (face (if (eq prop 'hilit-chg-delete)
483 'highlight-changes-delete
484 (nth 1 (member prop hilit-chg-list)))))
e287d328
RS
485 (if face
486 (progn
7c655cf6 487 ;; We must mark the face, that is the purpose of the overlay.
e287d328
RS
488 (overlay-put ov 'face face)
489 ;; I don't think we need to set evaporate since we should
490 ;; be controlling them!
491 (overlay-put ov 'evaporate t)
492 ;; We set the change property so we can tell this is one
493 ;; of our overlays (so we don't delete someone else's).
494 (overlay-put ov 'hilit-chg t)
495 )
3ec30bcb 496 (error "hilit-chg-make-ov: no face for prop: %s" prop))))
e287d328
RS
497
498(defun hilit-chg-hide-changes (&optional beg end)
499 "Remove face information for Highlight Changes mode.
500
f6ec4635 501The overlay containing the face is removed, but the text property
e287d328
RS
502containing the change information is retained.
503
3ec30bcb 504This is the opposite of `hilit-chg-display-changes'."
e287d328 505 (let ((start (or beg (point-min)))
7c655cf6
SM
506 (limit (or end (point-max))))
507 (dolist (p (overlays-in start limit))
e287d328 508 ;; don't delete the overlay if it isn't ours!
7c655cf6
SM
509 (if (overlay-get p 'hilit-chg)
510 (delete-overlay p)))))
511
e287d328
RS
512
513(defun hilit-chg-fixup (beg end)
3ec30bcb 514 "Fix change overlays in region between BEG and END.
e287d328
RS
515
516Ensure the overlays agree with the changes as determined from
2c470dc3 517the text properties of type `hilit-chg'."
e287d328 518 ;; Remove or alter overlays in region beg..end
01dcf284
SM
519 (remove-overlays beg end 'hilit-chg t)
520 (hilit-chg-display-changes beg end))
e287d328 521
2e819508
SM
522;; Inspired by font-lock. Something like this should be moved to subr.el.
523(defmacro highlight-save-buffer-state (&rest body)
524 "Bind variables according to VARLIST and eval BODY restoring buffer state."
525 (declare (indent 0) (debug t))
526 (let ((modified (make-symbol "modified")))
527 `(let* ((,modified (buffer-modified-p))
528 (inhibit-modification-hooks t)
529 deactivate-mark
530 ;; So we don't check the file's mtime.
531 buffer-file-name
532 buffer-file-truename)
533 (progn
534 ,@body)
535 (unless ,modified
536 (restore-buffer-modified-p nil)))))
537
e287d328 538;;;###autoload
71296446
JB
539(defun highlight-changes-remove-highlight (beg end)
540 "Remove the change face from the region between BEG and END.
e287d328
RS
541This allows you to manually remove highlighting from uninteresting changes."
542 (interactive "r")
2e819508 543 (highlight-save-buffer-state
ddd1e91e 544 (remove-text-properties beg end '(hilit-chg nil))
e287d328
RS
545 (hilit-chg-fixup beg end)))
546
71296446 547(defun hilit-chg-set-face-on-change (beg end leng-before
f87d9934 548 &optional no-property-change)
e287d328
RS
549 "Record changes and optionally display them in a distinctive face.
550`hilit-chg-set' adds this function to the `after-change-functions' hook."
551 ;;
552 ;; This function is called by the `after-change-functions' hook, which
553 ;; is how we are notified when text is changed.
99f08df4 554 ;; It is also called from `highlight-compare-with-file'.
e287d328
RS
555 ;;
556 ;; We do NOT want to simply do this if this is an undo command, because
557 ;; otherwise an undone change shows up as changed. While the properties
f6ec4635 558 ;; are automatically restored by undo, we must fix up the overlay.
e287d328 559 (save-match-data
06b60517
JB
560 (let (;;(beg-decr 1)
561 (end-incr 1)
562 (type 'hilit-chg))
e287d328 563 (if undo-in-progress
7c655cf6
SM
564 (if (and highlight-changes-mode
565 highlight-changes-visible-mode)
e287d328 566 (hilit-chg-fixup beg end))
2e819508
SM
567 (highlight-save-buffer-state
568 (if (and (= beg end) (> leng-before 0))
569 ;; deletion
570 (progn
571 ;; The eolp and bolp tests are a kludge! But they prevent
572 ;; rather nasty looking displays when deleting text at the end
573 ;; of line, such as normal corrections as one is typing and
574 ;; immediately makes a correction, and when deleting first
575 ;; character of a line.
576 ;; (if (= leng-before 1)
577 ;; (if (eolp)
578 ;; (setq beg-decr 0 end-incr 0)
579 ;; (if (bolp)
580 ;; (setq beg-decr 0))))
581 ;; (setq beg (max (- beg beg-decr) (point-min)))
582 (setq end (min (+ end end-incr) (point-max)))
583 (setq type 'hilit-chg-delete))
584 ;; Not a deletion.
585 ;; Most of the time the following is not necessary, but
586 ;; if the current text was marked as a deletion then
587 ;; the old overlay is still in effect, so if we add some
588 ;; text then remove the deletion marking, but set it to
e287d328
RS
589 ;; changed otherwise its highlighting disappears.
590 (if (eq (get-text-property end 'hilit-chg) 'hilit-chg-delete)
591 (progn
e287d328 592 (put-text-property end (+ end 1) 'hilit-chg 'hilit-chg)
7c655cf6
SM
593 (if highlight-changes-visible-mode
594 (hilit-chg-fixup beg (+ end 1))))))
2e819508
SM
595 (unless no-property-change
596 (put-text-property beg end 'hilit-chg type))
7c655cf6 597 (if (or highlight-changes-visible-mode no-property-change)
2e819508 598 (hilit-chg-make-ov type beg end)))))))
e287d328 599
7c655cf6 600(defun hilit-chg-update ()
f3b21763 601 "Update a buffer's highlight changes when visibility changed."
7c655cf6
SM
602 (if highlight-changes-visible-mode
603 ;; changes are visible
e287d328 604 (progn
7c655cf6 605 (setq hilit-chg-string highlight-changes-visible-string)
e287d328
RS
606 (or buffer-read-only
607 (hilit-chg-display-changes)))
7c655cf6
SM
608 ;; changes are invisible
609 (setq hilit-chg-string highlight-changes-invisible-string)
e287d328 610 (or buffer-read-only
7c655cf6
SM
611 (hilit-chg-hide-changes))))
612
613(defun hilit-chg-set ()
614 "Turn on Highlight Changes mode for this buffer."
615 (remove-hook 'after-change-functions 'hilit-chg-set-face-on-change t)
616 (hilit-chg-make-list)
617 (setq highlight-changes-mode t)
618 (setq highlight-changes-visible-mode highlight-changes-visibility-initial-state)
619 (hilit-chg-update)
e287d328 620 (force-mode-line-update)
7ebafc09 621 (add-hook 'after-change-functions 'hilit-chg-set-face-on-change nil t))
e287d328
RS
622
623(defun hilit-chg-clear ()
624 "Remove Highlight Changes mode for this buffer.
625This removes all saved change information."
626 (if buffer-read-only
627 ;; We print the buffer name because this function could be called
7c655cf6 628 ;; on many buffers from `global-highlight-changes-mode'.
e287d328
RS
629 (message "Cannot remove highlighting from read-only mode buffer %s"
630 (buffer-name))
631 (remove-hook 'after-change-functions 'hilit-chg-set-face-on-change t)
2e819508 632 (highlight-save-buffer-state
e287d328 633 (hilit-chg-hide-changes)
71296446 634 (hilit-chg-map-changes
06b60517 635 (lambda (_prop start stop)
2e819508 636 (remove-text-properties start stop '(hilit-chg nil)))))
e287d328 637 (setq highlight-changes-mode nil)
7c655cf6 638 (force-mode-line-update)))
e287d328 639
e287d328
RS
640
641;;;###autoload
642(defun highlight-changes-next-change ()
643 "Move to the beginning of the next change, if in Highlight Changes mode."
644 (interactive)
645 (if highlight-changes-mode
646 (let ((start (point))
647 prop)
648 (setq prop (get-text-property (point) 'hilit-chg))
649 (if prop
650 ;; we are in a change
651 (setq start (next-single-property-change (point) 'hilit-chg)))
652 (if start
653 (setq start (next-single-property-change start 'hilit-chg)))
654 (if start
655 (goto-char start)
656 (message "no next change")))
657 (message "This buffer is not in Highlight Changes mode.")))
658
659
660;;;###autoload
661(defun highlight-changes-previous-change ()
662 "Move to the beginning of the previous change, if in Highlight Changes mode."
663 (interactive)
664 (if highlight-changes-mode
665 (let ( (start (point)) (prop nil) )
666 (or (bobp)
667 (setq prop (get-text-property (1- (point)) 'hilit-chg)))
668 (if prop
669 ;; we are in a change
670 (setq start (previous-single-property-change (point) 'hilit-chg)))
671 (if start
672 (setq start (previous-single-property-change start 'hilit-chg)))
673 ;; special handling for the case where (point-min) is a change
674 (if start
675 (setq start (or (previous-single-property-change start 'hilit-chg)
676 (if (get-text-property (point-min) 'hilit-chg)
677 (point-min)))))
678 (if start
679 (goto-char start)
680 (message "no previous change")))
681 (message "This buffer is not in Highlight Changes mode.")))
682
e287d328
RS
683;; ========================================================================
684
e287d328 685(defun hilit-chg-make-list (&optional force)
3ec30bcb 686 "Construct `hilit-chg-list' and `highlight-changes-face-list'."
f6ec4635 687 ;; Constructs highlight-changes-face-list if necessary,
e287d328
RS
688 ;; and hilit-chg-list always:
689 ;; Maybe this should always be called when rotating a face
690 ;; so we pick up any changes?
691 (if (or (null highlight-changes-face-list) ; Don't do it if it
692 force) ; already exists unless FORCE non-nil.
b1412131 693 (let ((p highlight-changes-colors)
e287d328
RS
694 (n 1) name)
695 (setq highlight-changes-face-list nil)
696 (while p
a01853d7
MB
697 (setq name (intern (format "highlight-changes-%d" n)))
698 (copy-face 'highlight-changes name)
e287d328 699 (set-face-foreground name (car p))
71296446 700 (setq highlight-changes-face-list
e287d328
RS
701 (append highlight-changes-face-list (list name)))
702 (setq p (cdr p))
703 (setq n (1+ n)))))
a01853d7 704 (setq hilit-chg-list (list 'hilit-chg 'highlight-changes))
e287d328 705 (let ((p highlight-changes-face-list)
71296446 706 (n 1)
e287d328
RS
707 last-category last-face)
708 (while p
709 (setq last-category (intern (format "change-%d" n)))
a01853d7 710 ;; (setq last-face (intern (format "highlight-changes-%d" n)))
e287d328
RS
711 (setq last-face (car p))
712 (setq hilit-chg-list
713 (append hilit-chg-list
714 (list last-category last-face)))
715 (setq p (cdr p))
716 (setq n (1+ n)))
717 (setq hilit-chg-list
718 (append hilit-chg-list
3ec30bcb 719 (list last-category last-face)))))
e287d328
RS
720
721(defun hilit-chg-bump-change (prop start end)
3ec30bcb 722 "Increment (age) the Highlight Changes mode text property."
e287d328
RS
723 (let ( new-prop )
724 (if (eq prop 'hilit-chg-delete)
725 (setq new-prop (nth 2 hilit-chg-list))
3ec30bcb 726 (setq new-prop (nth 2 (member prop hilit-chg-list))))
e287d328
RS
727 (if prop
728 (put-text-property start end 'hilit-chg new-prop)
3ec30bcb 729 (message "%d-%d unknown property %s not changed" start end prop))))
e287d328
RS
730
731;;;###autoload
732(defun highlight-changes-rotate-faces ()
7c655cf6 733 "Rotate the faces if in Highlight Changes mode and the changes are visible.
e287d328 734
3ec30bcb
GM
735Current changes are displayed in the face described by the first element
736of `highlight-changes-face-list', one level older changes are shown in
e287d328
RS
737face described by the second element, and so on. Very old changes remain
738shown in the last face in the list.
739
2c470dc3
JB
740You can automatically rotate colors when the buffer is saved by adding
741this function to `write-file-functions' as a buffer-local value. To do
742this, eval the following in the buffer to be saved:
3ec30bcb 743
2c470dc3 744 \(add-hook 'write-file-functions 'highlight-changes-rotate-faces nil t)"
e287d328 745 (interactive)
7c655cf6 746 (when (and highlight-changes-mode highlight-changes-visible-mode)
05d6ece7
CY
747 (let ((modified (buffer-modified-p))
748 (inhibit-modification-hooks t))
749 ;; The `modified' related code tries to combine two goals: (1) Record the
750 ;; rotation in `buffer-undo-list' and (2) avoid setting the modified flag
751 ;; of the current buffer due to the rotation. We do this by inserting (in
752 ;; `buffer-undo-list') entries restoring buffer-modified-p to nil before
753 ;; and after the entry for the rotation.
2e819508
SM
754 ;; FIXME: this is no good: we need to test the `modified' state at the
755 ;; time of the undo, not at the time of the "do", otherwise the undo
756 ;; may erroneously clear the modified flag. --Stef
757 ;; (unless modified
758 ;; ;; Install the "before" entry.
759 ;; (push '(apply restore-buffer-modified-p nil) buffer-undo-list))
05d6ece7
CY
760 (unwind-protect
761 (progn
762 ;; ensure hilit-chg-list is made and up to date
763 (hilit-chg-make-list)
764 ;; remove our existing overlays
765 (hilit-chg-hide-changes)
766 ;; for each change text property, increment it
767 (hilit-chg-map-changes 'hilit-chg-bump-change)
7c655cf6
SM
768 ;; and display them
769 (hilit-chg-display-changes))
05d6ece7 770 (unless modified
2e819508
SM
771 ;; Install the "after" entry. FIXME: See above.
772 ;; (push '(apply restore-buffer-modified-p nil) buffer-undo-list)
05d6ece7
CY
773
774 (restore-buffer-modified-p nil)))))
2c470dc3 775 ;; This always returns nil so it is safe to use in write-file-functions
e287d328
RS
776 nil)
777
e287d328 778;; ========================================================================
f826bf67
EZ
779;; Comparing buffers/files
780;; These use ediff to find the differences.
781
782(defun highlight-markup-buffers
783 (buf-a file-a buf-b file-b &optional markup-a-only)
784 "Get differences between two buffers and set highlight changes.
785Both buffers are done unless optional parameter MARKUP-A-ONLY
786is non-nil."
7c655cf6
SM
787 (eval-and-compile
788 (require 'ediff-util))
f826bf67
EZ
789 (save-window-excursion
790 (let* (change-info
791 change-a change-b
792 a-start a-end len-a
793 b-start b-end len-b
794 (bufa-modified (buffer-modified-p buf-a))
795 (bufb-modified (buffer-modified-p buf-b))
796 (buf-a-read-only (with-current-buffer buf-a buffer-read-only))
797 (buf-b-read-only (with-current-buffer buf-b buffer-read-only))
798 temp-a temp-b)
799 (if (and file-a bufa-modified)
800 (if (y-or-n-p (format "Save buffer %s? " buf-a))
801 (with-current-buffer buf-a
802 (save-buffer)
803 (setq bufa-modified (buffer-modified-p buf-a)))
804 (setq file-a nil)))
805 (or file-a
806 (setq temp-a (setq file-a (ediff-make-temp-file buf-a nil))))
807
808 (if (and file-b bufb-modified)
809 (if (y-or-n-p (format "Save buffer %s? " buf-b))
810 (with-current-buffer buf-b
811 (save-buffer)
812 (setq bufb-modified (buffer-modified-p buf-b)))
813 (setq file-b nil)))
814 (or file-b
815 (setq temp-b (setq file-b (ediff-make-temp-file buf-b nil))))
816 (set-buffer buf-a)
7c655cf6 817 (highlight-changes-mode 1)
f826bf67 818 (or markup-a-only (with-current-buffer buf-b
7c655cf6 819 (highlight-changes-mode 1)))
f826bf67
EZ
820 (setq change-info (hilit-chg-get-diff-info buf-a file-a buf-b file-b))
821
822
823 (setq change-a (car change-info))
824 (setq change-b (car (cdr change-info)))
85fcb671 825
f826bf67
EZ
826 (hilit-chg-make-list)
827 (while change-a
828 (setq a-start (nth 0 (car change-a)))
829 (setq a-end (nth 1 (car change-a)))
830 (setq b-start (nth 0 (car change-b)))
831 (setq b-end (nth 1 (car change-b)))
832 (setq len-a (- a-end a-start))
833 (setq len-b (- b-end b-start))
834 (set-buffer buf-a)
835 (hilit-chg-set-face-on-change a-start a-end len-b buf-a-read-only)
836 (or markup-a-only
837 (with-current-buffer buf-b
838 (hilit-chg-set-face-on-change b-start b-end len-a
839 buf-b-read-only)
840 ))
841 (setq change-a (cdr change-a))
842 (setq change-b (cdr change-b)))
843 (or bufa-modified
844 (with-current-buffer buf-a (set-buffer-modified-p nil)))
845 (or bufb-modified
846 (with-current-buffer buf-b (set-buffer-modified-p nil)))
847 (if temp-a
848 (delete-file temp-a))
849 (if temp-b
850 (delete-file temp-b)))
851 ))
852
853;;;###autoload
854(defun highlight-compare-buffers (buf-a buf-b)
855"Compare two buffers and highlight the differences.
856
857The default is the current buffer and the one in the next window.
858
859If either buffer is modified and is visiting a file, you are prompted
860to save the file.
861
2c470dc3 862Unless the buffer is unmodified and visiting a file, the buffer is
f826bf67
EZ
863written to a temporary file for comparison.
864
865If a buffer is read-only, differences will be highlighted but no property
866changes are made, so \\[highlight-changes-next-change] and
867\\[highlight-changes-previous-change] will not work."
868 (interactive
85fcb671 869 (list
f826bf67
EZ
870 (get-buffer (read-buffer "buffer-a " (current-buffer) t))
871 (get-buffer
872 (read-buffer "buffer-b "
85fcb671 873 (window-buffer (next-window (selected-window))) t))))
f826bf67
EZ
874 (let ((file-a (buffer-file-name buf-a))
875 (file-b (buffer-file-name buf-b)))
876 (highlight-markup-buffers buf-a file-a buf-b file-b)
877 ))
e287d328
RS
878
879;;;###autoload
b32e3ef8
KH
880(defun highlight-compare-with-file (file-b)
881 "Compare this buffer with a file, and highlight differences.
e287d328 882
3ec30bcb
GM
883If the buffer has a backup filename, it is used as the default when
884this function is called interactively.
e287d328 885
3ec30bcb
GM
886If the current buffer is visiting the file being compared against, it
887also will have its differences highlighted. Otherwise, the file is
888read in temporarily but the buffer is deleted.
e287d328 889
3ec30bcb
GM
890If the buffer is read-only, differences will be highlighted but no property
891changes are made, so \\[highlight-changes-next-change] and
e287d328 892\\[highlight-changes-previous-change] will not work."
7c655cf6
SM
893 (interactive
894 (let ((file buffer-file-name)
895 (file-name nil)
896 (file-dir nil))
897 (and file
898 (setq file-name (file-name-nondirectory file)
899 file-dir (file-name-directory file)))
900 (setq file-name (make-backup-file-name file-name))
901 (unless (file-exists-p file-name)
902 (setq file-name nil))
903 (list (read-file-name
904 "Find to compare with: " ;; prompt
905 file-dir ;; directory
906 nil ;; default
907 nil ;; existing
908 file-name) ;; initial
909 )))
e287d328 910 (let* ((buf-a (current-buffer))
e287d328
RS
911 (file-a (buffer-file-name))
912 (existing-buf (get-file-buffer file-b))
913 (buf-b (or existing-buf
06b60517 914 (find-file-noselect file-b))))
f826bf67
EZ
915 (highlight-markup-buffers buf-a file-a buf-b file-b (not existing-buf))
916 (unless existing-buf
917 (kill-buffer buf-b))
918 ))
e287d328
RS
919
920
921(defun hilit-chg-get-diff-info (buf-a file-a buf-b file-b)
849b02b4
GM
922 ;; hilit-e,x,y are set by function hilit-chg-get-diff-list-hk.
923 (let (hilit-e hilit-x hilit-y)
e287d328
RS
924 (ediff-setup buf-a file-a buf-b file-b
925 nil nil ; buf-c file-C
926 'hilit-chg-get-diff-list-hk
927 (list (cons 'ediff-job-name 'something))
928 )
849b02b4
GM
929 (ediff-with-current-buffer hilit-e (ediff-really-quit nil))
930 (list hilit-x hilit-y)))
e287d328
RS
931
932
933(defun hilit-chg-get-diff-list-hk ()
849b02b4
GM
934 ;; hilit-e/x/y are dynamically bound by hilit-chg-get-diff-info
935 ;; which calls this function as a hook.
936 (defvar hilit-x) ; placate the byte-compiler
937 (defvar hilit-y)
938 (defvar hilit-e)
939 (setq hilit-e (current-buffer))
e287d328 940 (let ((n 0) extent p va vb a b)
849b02b4 941 (setq hilit-x nil hilit-y nil)
e287d328
RS
942 (while (< n ediff-number-of-differences)
943 (ediff-make-fine-diffs n)
944 (setq va (ediff-get-fine-diff-vector n 'A))
945 ;; va is a vector if there are fine differences
946 (if va
947 (setq a (append va nil))
f6ec4635 948 ;; if not, get the unrefined difference
e287d328 949 (setq va (ediff-get-difference n 'A))
3ec30bcb 950 (setq a (list (elt va 0))))
e287d328
RS
951 ;; a list a list
952 (setq p a)
953 (while p
954 (setq extent (list (overlay-start (car p))
955 (overlay-end (car p))))
956 (setq p (cdr p))
849b02b4 957 (setq hilit-x (append hilit-x (list extent) )));; while p
e287d328
RS
958 ;;
959 (setq vb (ediff-get-fine-diff-vector n 'B))
960 ;; vb is a vector
961 (if vb
962 (setq b (append vb nil))
f6ec4635 963 ;; if not, get the unrefined difference
e287d328 964 (setq vb (ediff-get-difference n 'B))
3ec30bcb 965 (setq b (list (elt vb 0))))
e287d328
RS
966 ;; b list a list
967 (setq p b)
968 (while p
969 (setq extent (list (overlay-start (car p))
970 (overlay-end (car p))))
971 (setq p (cdr p))
849b02b4 972 (setq hilit-y (append hilit-y (list extent) )))
3ec30bcb 973 (setq n (1+ n)));; while
e287d328
RS
974 ;; ediff-quit doesn't work here.
975 ;; No point in returning a value, since this is a hook function.
976 ))
977
7c655cf6 978;; ======================= global-highlight-changes-mode ==============
e287d328 979
e287d328 980;;;###autoload
f5422e78 981(define-globalized-minor-mode global-highlight-changes-mode
7c655cf6 982 highlight-changes-mode highlight-changes-mode-turn-on)
e287d328 983
7c655cf6
SM
984(define-obsolete-function-alias
985 'global-highlight-changes
f5422e78 986 'global-highlight-changes-mode "23.1")
7c655cf6
SM
987
988(defun highlight-changes-mode-turn-on ()
f3b21763
JB
989 "See if Highlight Changes mode should be turned on for this buffer.
990This is called when `global-highlight-changes-mode' is turned on."
e287d328
RS
991 (or highlight-changes-mode ; do nothing if already on
992 (if
993 (cond
994 ((null highlight-changes-global-modes)
995 nil)
996 ((functionp highlight-changes-global-modes)
997 (funcall highlight-changes-global-modes))
998 ((listp highlight-changes-global-modes)
999 (if (eq (car-safe highlight-changes-global-modes) 'not)
1000 (not (memq major-mode (cdr highlight-changes-global-modes)))
1001 (memq major-mode highlight-changes-global-modes)))
1002 (t
71296446 1003 (and
ddd1e91e 1004 (not (string-match "^[ *]" (buffer-name)))
3ec30bcb 1005 (buffer-file-name))))
7c655cf6
SM
1006 (highlight-changes-mode 1))
1007 ))
71296446 1008
e287d328 1009
abe5c13a
LH
1010;;;; Desktop support.
1011
1012;; Called by `desktop-create-buffer' to restore `highlight-changes-mode'.
1013(defun hilit-chg-desktop-restore (desktop-buffer-locals)
1014 (highlight-changes-mode
1015 (or (cdr (assq 'highlight-changes-mode desktop-buffer-locals)) 1)))
1016
1017(add-to-list 'desktop-minor-mode-handlers
1018 '(highlight-changes-mode . hilit-chg-desktop-restore))
1019
9e722067
LH
1020(add-to-list 'desktop-locals-to-save 'highlight-changes-mode)
1021
e287d328
RS
1022;; ===================== debug ==================
1023;; For debug & test use:
1024;;
1025;; (defun hilit-chg-debug-show (&optional beg end)
1026;; (interactive)
1027;; (message "--- hilit-chg-debug-show ---")
4f91a816
SM
1028;; (hilit-chg-map-changes (lambda (prop start end)
1029;; (message "%d-%d: %s" start end prop))
e287d328
RS
1030;; beg end
1031;; ))
71296446 1032;;
e287d328
RS
1033;; ================== end of debug ===============
1034
e287d328 1035(provide 'hilit-chg)
e287d328 1036
fdbd749a 1037;;; hilit-chg.el ends here