Spelling fixes.
[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
06e21633
CY
329 "Toggle highlighting changes in this buffer (Highlight Changes mode).
330With a prefix argument ARG, enable Highlight Changes mode if ARG
331is positive, and disable it otherwise. If called from Lisp,
332enable the mode if ARG is omitted or nil.
7c655cf6 333
06e21633
CY
334When Highlight Changes is enabled, changes are marked with a text
335property. Normally they are displayed in a distinctive face, but
336command \\[highlight-changes-visible-mode] can be used to toggles
337this on and off.
7c655cf6
SM
338
339Other functions for buffers in this mode include:
340\\[highlight-changes-next-change] - move point to beginning of next change
341\\[highlight-changes-previous-change] - move to beginning of previous change
342\\[highlight-changes-remove-highlight] - remove the change face from the region
343\\[highlight-changes-rotate-faces] - rotate different \"ages\" of changes
344through various faces.
345\\[highlight-compare-with-file] - mark text as changed by comparing this
346buffer with the contents of a file
7ebafc09 347\\[highlight-compare-buffers] highlights differences between two buffers."
7c655cf6
SM
348 nil ;; init-value
349 hilit-chg-string ;; lighter
350 nil ;; keymap
351 (if (or (display-color-p)
352 (and (fboundp 'x-display-grayscale-p) (x-display-grayscale-p)))
353 (progn
354 (if (and (eq this-command 'global-highlight-changes-mode)
355 (not highlight-changes-global-changes-existing-buffers))
356 ;; The global mode has toggled the value of the mode variable,
357 ;; but not other changes have been mode, so we are safe
358 ;; to retoggle it.
359 (setq highlight-changes-mode (not highlight-changes-mode)))
360 (if highlight-changes-mode
361 ;; it is being turned on
7c655cf6
SM
362 (hilit-chg-set)
363 ;; mode is turned off
364 (hilit-chg-clear)))
365 (message "Highlight Changes mode requires color or grayscale display")))
366
367
368;;;###autoload
369(define-minor-mode highlight-changes-visible-mode
fa463103 370 "Toggle visibility of highlighting due to Highlight Changes mode.
06e21633
CY
371With a prefix argument ARG, enable Highlight Changes Visible mode
372if ARG is positive, and disable it otherwise. If called from
373Lisp, enable the mode if ARG is omitted or nil.
7c655cf6 374
06e21633
CY
375Highlight Changes Visible mode only has an effect when Highlight
376Changes mode is on. When enabled, the changed text is displayed
7c655cf6
SM
377in a distinctive face.
378
379The default value can be customized with variable
06e21633 380`highlight-changes-visibility-initial-state'.
7c655cf6
SM
381
382This command does not itself set highlight-changes mode."
383
384 t ;; init-value
385 nil ;; lighter
386 nil ;; keymap
387
388 (hilit-chg-update)
389 )
390
391
06b60517 392(defun hilit-chg-cust-fix-changes-face-list (w _wc &optional event)
e287d328 393 ;; When customization function `highlight-changes-face-list' inserts a new
f6ec4635
JB
394 ;; face it uses the default face. We don't want the user to modify this
395 ;; face, so we rename the faces in the list on an insert. The rename is
e287d328
RS
396 ;; actually done by copying the faces so user-defined faces still remain
397 ;; in the same order.
398 ;; The notifying the parent is needed because without it changes to the
399 ;; faces are saved but not to the actual list itself.
400 (let ((old-list (widget-value w)))
401 (if (member 'default old-list)
402 (let
403 ((p (reverse old-list))
404 (n (length old-list))
405 new-name old-name
406 (new-list nil)
407 )
408 (while p
409 (setq old-name (car p))
a01853d7 410 (setq new-name (intern (format "highlight-changes-%d" n)))
e287d328
RS
411 (if (eq old-name new-name)
412 nil
413 ;; A new face has been inserted: we don't want to modify the
f6ec4635 414 ;; default face so copy it. Better, though, (I think) is to
e287d328 415 ;; make a new face have the same attributes as
a01853d7 416 ;; the `highlight-changes' face.
e287d328 417 (if (eq old-name 'default)
a01853d7 418 (copy-face 'highlight-changes new-name)
e287d328
RS
419 (copy-face old-name new-name)
420 ))
ea56cdf1 421 (setq new-list (append (list new-name) new-list))
e287d328
RS
422 (setq n (1- n))
423 (setq p (cdr p)))
424 (if (equal new-list (widget-value w))
425 nil ;; (message "notify: no change!")
426 (widget-value-set w new-list)
427 (widget-setup)
428 )
429 )
430 ;; (message "notify: no default here!")
431 ))
432 (let ((parent (widget-get w :parent)))
433 (when parent
3ec30bcb 434 (widget-apply parent :notify w event))))
e287d328
RS
435
436
437(defcustom highlight-changes-face-list nil
01dcf284 438 "A list of faces used when rotating changes.
e287d328 439Normally the variable is initialized to nil and the list is created from
b1412131 440`highlight-changes-colors' when needed. However, you can set this variable
e287d328 441to any list of faces. You will have to do this if you want faces which
85fcb671 442don't just differ from the `highlight-changes' face by the foreground color.
e287d328 443Otherwise, this list will be constructed when needed from
b1412131 444`highlight-changes-colors'."
e287d328 445 :type '(choice
71296446 446 (repeat
e287d328
RS
447 :notify hilit-chg-cust-fix-changes-face-list
448 face )
b1412131 449 (const :tag "Derive from highlight-changes-colors" nil)
e287d328 450 )
3ec30bcb 451 :group 'highlight-changes)
e287d328 452
e287d328 453
f3b21763 454(defun hilit-chg-map-changes (func &optional start-position end-position)
7c655cf6
SM
455 "Call function FUNC for each region used by Highlight Changes mode.
456If START-POSITION is nil, (point-min) is used.
457If END-POSITION is nil, (point-max) is used.
458FUNC is called with 3 params: PROPERTY START STOP."
e287d328
RS
459 (let ((start (or start-position (point-min)))
460 (limit (or end-position (point-max)))
461 prop end)
462 (while (and start (< start limit))
463 (setq prop (get-text-property start 'hilit-chg))
464 (setq end (text-property-not-all start limit 'hilit-chg prop))
465 (if prop
466 (funcall func prop start (or end limit)))
3ec30bcb 467 (setq start end))))
e287d328
RS
468
469
470(defun hilit-chg-display-changes (&optional beg end)
471 "Display face information for Highlight Changes mode.
472
7c655cf6
SM
473An overlay from BEG to END containing a change face is added from the
474information in the text property of type `hilit-chg'.
e287d328 475
3ec30bcb 476This is the opposite of `hilit-chg-hide-changes'."
e287d328
RS
477 (hilit-chg-map-changes 'hilit-chg-make-ov beg end))
478
479
480(defun hilit-chg-make-ov (prop start end)
f87d9934
RS
481 (or prop
482 (error "hilit-chg-make-ov: prop is nil"))
4c36be58 483 ;; For the region create overlays with a distinctive face
7c655cf6 484 ;; and the text property 'hilit-chg.
e287d328 485 (let ((ov (make-overlay start end))
01dcf284
SM
486 (face (if (eq prop 'hilit-chg-delete)
487 'highlight-changes-delete
488 (nth 1 (member prop hilit-chg-list)))))
e287d328
RS
489 (if face
490 (progn
7c655cf6 491 ;; We must mark the face, that is the purpose of the overlay.
e287d328
RS
492 (overlay-put ov 'face face)
493 ;; I don't think we need to set evaporate since we should
494 ;; be controlling them!
495 (overlay-put ov 'evaporate t)
496 ;; We set the change property so we can tell this is one
497 ;; of our overlays (so we don't delete someone else's).
498 (overlay-put ov 'hilit-chg t)
499 )
3ec30bcb 500 (error "hilit-chg-make-ov: no face for prop: %s" prop))))
e287d328
RS
501
502(defun hilit-chg-hide-changes (&optional beg end)
503 "Remove face information for Highlight Changes mode.
504
f6ec4635 505The overlay containing the face is removed, but the text property
e287d328
RS
506containing the change information is retained.
507
3ec30bcb 508This is the opposite of `hilit-chg-display-changes'."
e287d328 509 (let ((start (or beg (point-min)))
7c655cf6
SM
510 (limit (or end (point-max))))
511 (dolist (p (overlays-in start limit))
e287d328 512 ;; don't delete the overlay if it isn't ours!
7c655cf6
SM
513 (if (overlay-get p 'hilit-chg)
514 (delete-overlay p)))))
515
e287d328
RS
516
517(defun hilit-chg-fixup (beg end)
3ec30bcb 518 "Fix change overlays in region between BEG and END.
e287d328
RS
519
520Ensure the overlays agree with the changes as determined from
2c470dc3 521the text properties of type `hilit-chg'."
e287d328 522 ;; Remove or alter overlays in region beg..end
01dcf284
SM
523 (remove-overlays beg end 'hilit-chg t)
524 (hilit-chg-display-changes beg end))
e287d328 525
2e819508
SM
526;; Inspired by font-lock. Something like this should be moved to subr.el.
527(defmacro highlight-save-buffer-state (&rest body)
528 "Bind variables according to VARLIST and eval BODY restoring buffer state."
529 (declare (indent 0) (debug t))
530 (let ((modified (make-symbol "modified")))
531 `(let* ((,modified (buffer-modified-p))
532 (inhibit-modification-hooks t)
533 deactivate-mark
534 ;; So we don't check the file's mtime.
535 buffer-file-name
536 buffer-file-truename)
537 (progn
538 ,@body)
539 (unless ,modified
540 (restore-buffer-modified-p nil)))))
541
e287d328 542;;;###autoload
71296446
JB
543(defun highlight-changes-remove-highlight (beg end)
544 "Remove the change face from the region between BEG and END.
e287d328
RS
545This allows you to manually remove highlighting from uninteresting changes."
546 (interactive "r")
2e819508 547 (highlight-save-buffer-state
ddd1e91e 548 (remove-text-properties beg end '(hilit-chg nil))
e287d328
RS
549 (hilit-chg-fixup beg end)))
550
71296446 551(defun hilit-chg-set-face-on-change (beg end leng-before
f87d9934 552 &optional no-property-change)
e287d328
RS
553 "Record changes and optionally display them in a distinctive face.
554`hilit-chg-set' adds this function to the `after-change-functions' hook."
555 ;;
556 ;; This function is called by the `after-change-functions' hook, which
557 ;; is how we are notified when text is changed.
99f08df4 558 ;; It is also called from `highlight-compare-with-file'.
e287d328
RS
559 ;;
560 ;; We do NOT want to simply do this if this is an undo command, because
561 ;; otherwise an undone change shows up as changed. While the properties
f6ec4635 562 ;; are automatically restored by undo, we must fix up the overlay.
e287d328 563 (save-match-data
06b60517
JB
564 (let (;;(beg-decr 1)
565 (end-incr 1)
566 (type 'hilit-chg))
e287d328 567 (if undo-in-progress
7c655cf6
SM
568 (if (and highlight-changes-mode
569 highlight-changes-visible-mode)
e287d328 570 (hilit-chg-fixup beg end))
2e819508
SM
571 (highlight-save-buffer-state
572 (if (and (= beg end) (> leng-before 0))
573 ;; deletion
574 (progn
575 ;; The eolp and bolp tests are a kludge! But they prevent
576 ;; rather nasty looking displays when deleting text at the end
577 ;; of line, such as normal corrections as one is typing and
578 ;; immediately makes a correction, and when deleting first
579 ;; character of a line.
580 ;; (if (= leng-before 1)
581 ;; (if (eolp)
582 ;; (setq beg-decr 0 end-incr 0)
583 ;; (if (bolp)
584 ;; (setq beg-decr 0))))
585 ;; (setq beg (max (- beg beg-decr) (point-min)))
586 (setq end (min (+ end end-incr) (point-max)))
587 (setq type 'hilit-chg-delete))
588 ;; Not a deletion.
589 ;; Most of the time the following is not necessary, but
590 ;; if the current text was marked as a deletion then
591 ;; the old overlay is still in effect, so if we add some
592 ;; text then remove the deletion marking, but set it to
e287d328
RS
593 ;; changed otherwise its highlighting disappears.
594 (if (eq (get-text-property end 'hilit-chg) 'hilit-chg-delete)
595 (progn
e287d328 596 (put-text-property end (+ end 1) 'hilit-chg 'hilit-chg)
7c655cf6
SM
597 (if highlight-changes-visible-mode
598 (hilit-chg-fixup beg (+ end 1))))))
2e819508
SM
599 (unless no-property-change
600 (put-text-property beg end 'hilit-chg type))
7c655cf6 601 (if (or highlight-changes-visible-mode no-property-change)
2e819508 602 (hilit-chg-make-ov type beg end)))))))
e287d328 603
7c655cf6 604(defun hilit-chg-update ()
f3b21763 605 "Update a buffer's highlight changes when visibility changed."
7c655cf6
SM
606 (if highlight-changes-visible-mode
607 ;; changes are visible
e287d328 608 (progn
7c655cf6 609 (setq hilit-chg-string highlight-changes-visible-string)
e287d328
RS
610 (or buffer-read-only
611 (hilit-chg-display-changes)))
7c655cf6
SM
612 ;; changes are invisible
613 (setq hilit-chg-string highlight-changes-invisible-string)
e287d328 614 (or buffer-read-only
7c655cf6
SM
615 (hilit-chg-hide-changes))))
616
617(defun hilit-chg-set ()
618 "Turn on Highlight Changes mode for this buffer."
619 (remove-hook 'after-change-functions 'hilit-chg-set-face-on-change t)
620 (hilit-chg-make-list)
621 (setq highlight-changes-mode t)
622 (setq highlight-changes-visible-mode highlight-changes-visibility-initial-state)
623 (hilit-chg-update)
e287d328 624 (force-mode-line-update)
7ebafc09 625 (add-hook 'after-change-functions 'hilit-chg-set-face-on-change nil t))
e287d328
RS
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
7c655cf6 632 ;; on many buffers from `global-highlight-changes-mode'.
e287d328
RS
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)
2e819508 636 (highlight-save-buffer-state
e287d328 637 (hilit-chg-hide-changes)
71296446 638 (hilit-chg-map-changes
06b60517 639 (lambda (_prop start stop)
2e819508 640 (remove-text-properties start stop '(hilit-chg nil)))))
e287d328 641 (setq highlight-changes-mode nil)
7c655cf6 642 (force-mode-line-update)))
e287d328 643
e287d328
RS
644
645;;;###autoload
646(defun highlight-changes-next-change ()
647 "Move to the beginning of the next change, if in Highlight Changes mode."
648 (interactive)
649 (if highlight-changes-mode
650 (let ((start (point))
651 prop)
652 (setq prop (get-text-property (point) 'hilit-chg))
653 (if prop
654 ;; we are in a change
655 (setq start (next-single-property-change (point) 'hilit-chg)))
656 (if start
657 (setq start (next-single-property-change start 'hilit-chg)))
658 (if start
659 (goto-char start)
660 (message "no next change")))
661 (message "This buffer is not in Highlight Changes mode.")))
662
663
664;;;###autoload
665(defun highlight-changes-previous-change ()
666 "Move to the beginning of the previous change, if in Highlight Changes mode."
667 (interactive)
668 (if highlight-changes-mode
669 (let ( (start (point)) (prop nil) )
670 (or (bobp)
671 (setq prop (get-text-property (1- (point)) 'hilit-chg)))
672 (if prop
673 ;; we are in a change
674 (setq start (previous-single-property-change (point) 'hilit-chg)))
675 (if start
676 (setq start (previous-single-property-change start 'hilit-chg)))
677 ;; special handling for the case where (point-min) is a change
678 (if start
679 (setq start (or (previous-single-property-change start 'hilit-chg)
680 (if (get-text-property (point-min) 'hilit-chg)
681 (point-min)))))
682 (if start
683 (goto-char start)
684 (message "no previous change")))
685 (message "This buffer is not in Highlight Changes mode.")))
686
e287d328
RS
687;; ========================================================================
688
e287d328 689(defun hilit-chg-make-list (&optional force)
3ec30bcb 690 "Construct `hilit-chg-list' and `highlight-changes-face-list'."
f6ec4635 691 ;; Constructs highlight-changes-face-list if necessary,
e287d328
RS
692 ;; and hilit-chg-list always:
693 ;; Maybe this should always be called when rotating a face
694 ;; so we pick up any changes?
695 (if (or (null highlight-changes-face-list) ; Don't do it if it
696 force) ; already exists unless FORCE non-nil.
b1412131 697 (let ((p highlight-changes-colors)
e287d328
RS
698 (n 1) name)
699 (setq highlight-changes-face-list nil)
700 (while p
a01853d7
MB
701 (setq name (intern (format "highlight-changes-%d" n)))
702 (copy-face 'highlight-changes name)
e287d328 703 (set-face-foreground name (car p))
71296446 704 (setq highlight-changes-face-list
e287d328
RS
705 (append highlight-changes-face-list (list name)))
706 (setq p (cdr p))
707 (setq n (1+ n)))))
a01853d7 708 (setq hilit-chg-list (list 'hilit-chg 'highlight-changes))
e287d328 709 (let ((p highlight-changes-face-list)
71296446 710 (n 1)
e287d328
RS
711 last-category last-face)
712 (while p
713 (setq last-category (intern (format "change-%d" n)))
a01853d7 714 ;; (setq last-face (intern (format "highlight-changes-%d" n)))
e287d328
RS
715 (setq last-face (car p))
716 (setq hilit-chg-list
717 (append hilit-chg-list
718 (list last-category last-face)))
719 (setq p (cdr p))
720 (setq n (1+ n)))
721 (setq hilit-chg-list
722 (append hilit-chg-list
3ec30bcb 723 (list last-category last-face)))))
e287d328
RS
724
725(defun hilit-chg-bump-change (prop start end)
3ec30bcb 726 "Increment (age) the Highlight Changes mode text property."
e287d328
RS
727 (let ( new-prop )
728 (if (eq prop 'hilit-chg-delete)
729 (setq new-prop (nth 2 hilit-chg-list))
3ec30bcb 730 (setq new-prop (nth 2 (member prop hilit-chg-list))))
e287d328
RS
731 (if prop
732 (put-text-property start end 'hilit-chg new-prop)
3ec30bcb 733 (message "%d-%d unknown property %s not changed" start end prop))))
e287d328
RS
734
735;;;###autoload
736(defun highlight-changes-rotate-faces ()
7c655cf6 737 "Rotate the faces if in Highlight Changes mode and the changes are visible.
e287d328 738
3ec30bcb
GM
739Current changes are displayed in the face described by the first element
740of `highlight-changes-face-list', one level older changes are shown in
e287d328
RS
741face described by the second element, and so on. Very old changes remain
742shown in the last face in the list.
743
2c470dc3
JB
744You can automatically rotate colors when the buffer is saved by adding
745this function to `write-file-functions' as a buffer-local value. To do
746this, eval the following in the buffer to be saved:
3ec30bcb 747
2c470dc3 748 \(add-hook 'write-file-functions 'highlight-changes-rotate-faces nil t)"
e287d328 749 (interactive)
7c655cf6 750 (when (and highlight-changes-mode highlight-changes-visible-mode)
05d6ece7
CY
751 (let ((modified (buffer-modified-p))
752 (inhibit-modification-hooks t))
753 ;; The `modified' related code tries to combine two goals: (1) Record the
754 ;; rotation in `buffer-undo-list' and (2) avoid setting the modified flag
755 ;; of the current buffer due to the rotation. We do this by inserting (in
756 ;; `buffer-undo-list') entries restoring buffer-modified-p to nil before
757 ;; and after the entry for the rotation.
2e819508
SM
758 ;; FIXME: this is no good: we need to test the `modified' state at the
759 ;; time of the undo, not at the time of the "do", otherwise the undo
760 ;; may erroneously clear the modified flag. --Stef
761 ;; (unless modified
762 ;; ;; Install the "before" entry.
763 ;; (push '(apply restore-buffer-modified-p nil) buffer-undo-list))
05d6ece7
CY
764 (unwind-protect
765 (progn
766 ;; ensure hilit-chg-list is made and up to date
767 (hilit-chg-make-list)
768 ;; remove our existing overlays
769 (hilit-chg-hide-changes)
770 ;; for each change text property, increment it
771 (hilit-chg-map-changes 'hilit-chg-bump-change)
7c655cf6
SM
772 ;; and display them
773 (hilit-chg-display-changes))
05d6ece7 774 (unless modified
2e819508
SM
775 ;; Install the "after" entry. FIXME: See above.
776 ;; (push '(apply restore-buffer-modified-p nil) buffer-undo-list)
05d6ece7
CY
777
778 (restore-buffer-modified-p nil)))))
2c470dc3 779 ;; This always returns nil so it is safe to use in write-file-functions
e287d328
RS
780 nil)
781
e287d328 782;; ========================================================================
f826bf67
EZ
783;; Comparing buffers/files
784;; These use ediff to find the differences.
785
786(defun highlight-markup-buffers
787 (buf-a file-a buf-b file-b &optional markup-a-only)
788 "Get differences between two buffers and set highlight changes.
789Both buffers are done unless optional parameter MARKUP-A-ONLY
790is non-nil."
7c655cf6
SM
791 (eval-and-compile
792 (require 'ediff-util))
f826bf67
EZ
793 (save-window-excursion
794 (let* (change-info
795 change-a change-b
796 a-start a-end len-a
797 b-start b-end len-b
798 (bufa-modified (buffer-modified-p buf-a))
799 (bufb-modified (buffer-modified-p buf-b))
800 (buf-a-read-only (with-current-buffer buf-a buffer-read-only))
801 (buf-b-read-only (with-current-buffer buf-b buffer-read-only))
802 temp-a temp-b)
803 (if (and file-a bufa-modified)
804 (if (y-or-n-p (format "Save buffer %s? " buf-a))
805 (with-current-buffer buf-a
806 (save-buffer)
807 (setq bufa-modified (buffer-modified-p buf-a)))
808 (setq file-a nil)))
809 (or file-a
810 (setq temp-a (setq file-a (ediff-make-temp-file buf-a nil))))
811
812 (if (and file-b bufb-modified)
813 (if (y-or-n-p (format "Save buffer %s? " buf-b))
814 (with-current-buffer buf-b
815 (save-buffer)
816 (setq bufb-modified (buffer-modified-p buf-b)))
817 (setq file-b nil)))
818 (or file-b
819 (setq temp-b (setq file-b (ediff-make-temp-file buf-b nil))))
820 (set-buffer buf-a)
7c655cf6 821 (highlight-changes-mode 1)
f826bf67 822 (or markup-a-only (with-current-buffer buf-b
7c655cf6 823 (highlight-changes-mode 1)))
f826bf67
EZ
824 (setq change-info (hilit-chg-get-diff-info buf-a file-a buf-b file-b))
825
826
827 (setq change-a (car change-info))
828 (setq change-b (car (cdr change-info)))
85fcb671 829
f826bf67
EZ
830 (hilit-chg-make-list)
831 (while change-a
832 (setq a-start (nth 0 (car change-a)))
833 (setq a-end (nth 1 (car change-a)))
834 (setq b-start (nth 0 (car change-b)))
835 (setq b-end (nth 1 (car change-b)))
836 (setq len-a (- a-end a-start))
837 (setq len-b (- b-end b-start))
838 (set-buffer buf-a)
839 (hilit-chg-set-face-on-change a-start a-end len-b buf-a-read-only)
840 (or markup-a-only
841 (with-current-buffer buf-b
842 (hilit-chg-set-face-on-change b-start b-end len-a
843 buf-b-read-only)
844 ))
845 (setq change-a (cdr change-a))
846 (setq change-b (cdr change-b)))
847 (or bufa-modified
848 (with-current-buffer buf-a (set-buffer-modified-p nil)))
849 (or bufb-modified
850 (with-current-buffer buf-b (set-buffer-modified-p nil)))
851 (if temp-a
852 (delete-file temp-a))
853 (if temp-b
854 (delete-file temp-b)))
855 ))
856
857;;;###autoload
858(defun highlight-compare-buffers (buf-a buf-b)
859"Compare two buffers and highlight the differences.
860
861The default is the current buffer and the one in the next window.
862
863If either buffer is modified and is visiting a file, you are prompted
864to save the file.
865
2c470dc3 866Unless the buffer is unmodified and visiting a file, the buffer is
f826bf67
EZ
867written to a temporary file for comparison.
868
869If a buffer is read-only, differences will be highlighted but no property
870changes are made, so \\[highlight-changes-next-change] and
871\\[highlight-changes-previous-change] will not work."
872 (interactive
85fcb671 873 (list
f826bf67
EZ
874 (get-buffer (read-buffer "buffer-a " (current-buffer) t))
875 (get-buffer
876 (read-buffer "buffer-b "
85fcb671 877 (window-buffer (next-window (selected-window))) t))))
f826bf67
EZ
878 (let ((file-a (buffer-file-name buf-a))
879 (file-b (buffer-file-name buf-b)))
880 (highlight-markup-buffers buf-a file-a buf-b file-b)
881 ))
e287d328
RS
882
883;;;###autoload
b32e3ef8
KH
884(defun highlight-compare-with-file (file-b)
885 "Compare this buffer with a file, and highlight differences.
e287d328 886
3ec30bcb
GM
887If the buffer has a backup filename, it is used as the default when
888this function is called interactively.
e287d328 889
3ec30bcb
GM
890If the current buffer is visiting the file being compared against, it
891also will have its differences highlighted. Otherwise, the file is
892read in temporarily but the buffer is deleted.
e287d328 893
3ec30bcb
GM
894If the buffer is read-only, differences will be highlighted but no property
895changes are made, so \\[highlight-changes-next-change] and
e287d328 896\\[highlight-changes-previous-change] will not work."
7c655cf6
SM
897 (interactive
898 (let ((file buffer-file-name)
899 (file-name nil)
900 (file-dir nil))
901 (and file
902 (setq file-name (file-name-nondirectory file)
903 file-dir (file-name-directory file)))
904 (setq file-name (make-backup-file-name file-name))
905 (unless (file-exists-p file-name)
906 (setq file-name nil))
907 (list (read-file-name
908 "Find to compare with: " ;; prompt
909 file-dir ;; directory
910 nil ;; default
911 nil ;; existing
912 file-name) ;; initial
913 )))
e287d328 914 (let* ((buf-a (current-buffer))
e287d328
RS
915 (file-a (buffer-file-name))
916 (existing-buf (get-file-buffer file-b))
917 (buf-b (or existing-buf
06b60517 918 (find-file-noselect file-b))))
f826bf67
EZ
919 (highlight-markup-buffers buf-a file-a buf-b file-b (not existing-buf))
920 (unless existing-buf
921 (kill-buffer buf-b))
922 ))
e287d328
RS
923
924
925(defun hilit-chg-get-diff-info (buf-a file-a buf-b file-b)
849b02b4
GM
926 ;; hilit-e,x,y are set by function hilit-chg-get-diff-list-hk.
927 (let (hilit-e hilit-x hilit-y)
e287d328
RS
928 (ediff-setup buf-a file-a buf-b file-b
929 nil nil ; buf-c file-C
930 'hilit-chg-get-diff-list-hk
931 (list (cons 'ediff-job-name 'something))
932 )
849b02b4
GM
933 (ediff-with-current-buffer hilit-e (ediff-really-quit nil))
934 (list hilit-x hilit-y)))
e287d328
RS
935
936
937(defun hilit-chg-get-diff-list-hk ()
849b02b4
GM
938 ;; hilit-e/x/y are dynamically bound by hilit-chg-get-diff-info
939 ;; which calls this function as a hook.
940 (defvar hilit-x) ; placate the byte-compiler
941 (defvar hilit-y)
942 (defvar hilit-e)
943 (setq hilit-e (current-buffer))
e287d328 944 (let ((n 0) extent p va vb a b)
849b02b4 945 (setq hilit-x nil hilit-y nil)
e287d328
RS
946 (while (< n ediff-number-of-differences)
947 (ediff-make-fine-diffs n)
948 (setq va (ediff-get-fine-diff-vector n 'A))
949 ;; va is a vector if there are fine differences
950 (if va
951 (setq a (append va nil))
f6ec4635 952 ;; if not, get the unrefined difference
e287d328 953 (setq va (ediff-get-difference n 'A))
3ec30bcb 954 (setq a (list (elt va 0))))
e287d328
RS
955 ;; a list a list
956 (setq p a)
957 (while p
958 (setq extent (list (overlay-start (car p))
959 (overlay-end (car p))))
960 (setq p (cdr p))
849b02b4 961 (setq hilit-x (append hilit-x (list extent) )));; while p
e287d328
RS
962 ;;
963 (setq vb (ediff-get-fine-diff-vector n 'B))
964 ;; vb is a vector
965 (if vb
966 (setq b (append vb nil))
f6ec4635 967 ;; if not, get the unrefined difference
e287d328 968 (setq vb (ediff-get-difference n 'B))
3ec30bcb 969 (setq b (list (elt vb 0))))
e287d328
RS
970 ;; b list a list
971 (setq p b)
972 (while p
973 (setq extent (list (overlay-start (car p))
974 (overlay-end (car p))))
975 (setq p (cdr p))
849b02b4 976 (setq hilit-y (append hilit-y (list extent) )))
3ec30bcb 977 (setq n (1+ n)));; while
e287d328
RS
978 ;; ediff-quit doesn't work here.
979 ;; No point in returning a value, since this is a hook function.
980 ))
981
7c655cf6 982;; ======================= global-highlight-changes-mode ==============
e287d328 983
e287d328 984;;;###autoload
f5422e78 985(define-globalized-minor-mode global-highlight-changes-mode
7c655cf6 986 highlight-changes-mode highlight-changes-mode-turn-on)
e287d328 987
7c655cf6
SM
988(define-obsolete-function-alias
989 'global-highlight-changes
f5422e78 990 'global-highlight-changes-mode "23.1")
7c655cf6
SM
991
992(defun highlight-changes-mode-turn-on ()
f3b21763
JB
993 "See if Highlight Changes mode should be turned on for this buffer.
994This is called when `global-highlight-changes-mode' is turned on."
e287d328
RS
995 (or highlight-changes-mode ; do nothing if already on
996 (if
997 (cond
998 ((null highlight-changes-global-modes)
999 nil)
1000 ((functionp highlight-changes-global-modes)
1001 (funcall highlight-changes-global-modes))
1002 ((listp highlight-changes-global-modes)
1003 (if (eq (car-safe highlight-changes-global-modes) 'not)
1004 (not (memq major-mode (cdr highlight-changes-global-modes)))
1005 (memq major-mode highlight-changes-global-modes)))
1006 (t
71296446 1007 (and
ddd1e91e 1008 (not (string-match "^[ *]" (buffer-name)))
3ec30bcb 1009 (buffer-file-name))))
7c655cf6
SM
1010 (highlight-changes-mode 1))
1011 ))
71296446 1012
e287d328 1013
abe5c13a
LH
1014;;;; Desktop support.
1015
1016;; Called by `desktop-create-buffer' to restore `highlight-changes-mode'.
1017(defun hilit-chg-desktop-restore (desktop-buffer-locals)
1018 (highlight-changes-mode
1019 (or (cdr (assq 'highlight-changes-mode desktop-buffer-locals)) 1)))
1020
1021(add-to-list 'desktop-minor-mode-handlers
1022 '(highlight-changes-mode . hilit-chg-desktop-restore))
1023
9e722067
LH
1024(add-to-list 'desktop-locals-to-save 'highlight-changes-mode)
1025
e287d328
RS
1026;; ===================== debug ==================
1027;; For debug & test use:
1028;;
1029;; (defun hilit-chg-debug-show (&optional beg end)
1030;; (interactive)
1031;; (message "--- hilit-chg-debug-show ---")
4f91a816
SM
1032;; (hilit-chg-map-changes (lambda (prop start end)
1033;; (message "%d-%d: %s" start end prop))
e287d328
RS
1034;; beg end
1035;; ))
71296446 1036;;
e287d328
RS
1037;; ================== end of debug ===============
1038
e287d328 1039(provide 'hilit-chg)
e287d328 1040
fdbd749a 1041;;; hilit-chg.el ends here