* lisp/button.el: Handle buttons in display text-properties.
[bpt/emacs.git] / lisp / hilit-chg.el
CommitLineData
e287d328
RS
1;;; hilit-chg.el --- minor mode displaying buffer changes with special face
2
acaf905b 3;; Copyright (C) 1998, 2000-2012 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 571 (highlight-save-buffer-state
3a21537c
LW
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 the user adds some
592 ;; text where she earlier deleted text, we have to remove the
593 ;; deletion marking, and replace it explicitly with a `changed'
594 ;; marking, otherwise its highlighting would disappear.
595 (if (eq (get-text-property end 'hilit-chg) 'hilit-chg-delete)
596 (save-restriction
597 (widen)
598 (put-text-property end (+ end 1) 'hilit-chg 'hilit-chg)
599 (if highlight-changes-visible-mode
600 (hilit-chg-fixup beg (+ end 1))))))
601 (unless no-property-change
602 (put-text-property beg end 'hilit-chg type))
603 (if (or highlight-changes-visible-mode no-property-change)
604 (hilit-chg-make-ov type beg end)))))))
e287d328 605
7c655cf6 606(defun hilit-chg-update ()
f3b21763 607 "Update a buffer's highlight changes when visibility changed."
7c655cf6
SM
608 (if highlight-changes-visible-mode
609 ;; changes are visible
e287d328 610 (progn
7c655cf6 611 (setq hilit-chg-string highlight-changes-visible-string)
e287d328
RS
612 (or buffer-read-only
613 (hilit-chg-display-changes)))
7c655cf6
SM
614 ;; changes are invisible
615 (setq hilit-chg-string highlight-changes-invisible-string)
e287d328 616 (or buffer-read-only
7c655cf6
SM
617 (hilit-chg-hide-changes))))
618
619(defun hilit-chg-set ()
620 "Turn on Highlight Changes mode for this buffer."
621 (remove-hook 'after-change-functions 'hilit-chg-set-face-on-change t)
622 (hilit-chg-make-list)
623 (setq highlight-changes-mode t)
624 (setq highlight-changes-visible-mode highlight-changes-visibility-initial-state)
625 (hilit-chg-update)
e287d328 626 (force-mode-line-update)
7ebafc09 627 (add-hook 'after-change-functions 'hilit-chg-set-face-on-change nil t))
e287d328
RS
628
629(defun hilit-chg-clear ()
630 "Remove Highlight Changes mode for this buffer.
631This removes all saved change information."
632 (if buffer-read-only
633 ;; We print the buffer name because this function could be called
7c655cf6 634 ;; on many buffers from `global-highlight-changes-mode'.
e287d328
RS
635 (message "Cannot remove highlighting from read-only mode buffer %s"
636 (buffer-name))
637 (remove-hook 'after-change-functions 'hilit-chg-set-face-on-change t)
2e819508 638 (highlight-save-buffer-state
e287d328 639 (hilit-chg-hide-changes)
71296446 640 (hilit-chg-map-changes
06b60517 641 (lambda (_prop start stop)
2e819508 642 (remove-text-properties start stop '(hilit-chg nil)))))
e287d328 643 (setq highlight-changes-mode nil)
7c655cf6 644 (force-mode-line-update)))
e287d328 645
e287d328
RS
646
647;;;###autoload
648(defun highlight-changes-next-change ()
649 "Move to the beginning of the next change, if in Highlight Changes mode."
650 (interactive)
651 (if highlight-changes-mode
652 (let ((start (point))
653 prop)
654 (setq prop (get-text-property (point) 'hilit-chg))
655 (if prop
656 ;; we are in a change
657 (setq start (next-single-property-change (point) 'hilit-chg)))
658 (if start
659 (setq start (next-single-property-change start 'hilit-chg)))
660 (if start
661 (goto-char start)
662 (message "no next change")))
663 (message "This buffer is not in Highlight Changes mode.")))
664
665
666;;;###autoload
667(defun highlight-changes-previous-change ()
668 "Move to the beginning of the previous change, if in Highlight Changes mode."
669 (interactive)
670 (if highlight-changes-mode
671 (let ( (start (point)) (prop nil) )
672 (or (bobp)
673 (setq prop (get-text-property (1- (point)) 'hilit-chg)))
674 (if prop
675 ;; we are in a change
676 (setq start (previous-single-property-change (point) 'hilit-chg)))
677 (if start
678 (setq start (previous-single-property-change start 'hilit-chg)))
679 ;; special handling for the case where (point-min) is a change
680 (if start
681 (setq start (or (previous-single-property-change start 'hilit-chg)
682 (if (get-text-property (point-min) 'hilit-chg)
683 (point-min)))))
684 (if start
685 (goto-char start)
686 (message "no previous change")))
687 (message "This buffer is not in Highlight Changes mode.")))
688
e287d328
RS
689;; ========================================================================
690
e287d328 691(defun hilit-chg-make-list (&optional force)
3ec30bcb 692 "Construct `hilit-chg-list' and `highlight-changes-face-list'."
f6ec4635 693 ;; Constructs highlight-changes-face-list if necessary,
e287d328
RS
694 ;; and hilit-chg-list always:
695 ;; Maybe this should always be called when rotating a face
696 ;; so we pick up any changes?
697 (if (or (null highlight-changes-face-list) ; Don't do it if it
698 force) ; already exists unless FORCE non-nil.
b1412131 699 (let ((p highlight-changes-colors)
e287d328
RS
700 (n 1) name)
701 (setq highlight-changes-face-list nil)
702 (while p
a01853d7
MB
703 (setq name (intern (format "highlight-changes-%d" n)))
704 (copy-face 'highlight-changes name)
e287d328 705 (set-face-foreground name (car p))
71296446 706 (setq highlight-changes-face-list
e287d328
RS
707 (append highlight-changes-face-list (list name)))
708 (setq p (cdr p))
709 (setq n (1+ n)))))
a01853d7 710 (setq hilit-chg-list (list 'hilit-chg 'highlight-changes))
e287d328 711 (let ((p highlight-changes-face-list)
71296446 712 (n 1)
e287d328
RS
713 last-category last-face)
714 (while p
715 (setq last-category (intern (format "change-%d" n)))
a01853d7 716 ;; (setq last-face (intern (format "highlight-changes-%d" n)))
e287d328
RS
717 (setq last-face (car p))
718 (setq hilit-chg-list
719 (append hilit-chg-list
720 (list last-category last-face)))
721 (setq p (cdr p))
722 (setq n (1+ n)))
723 (setq hilit-chg-list
724 (append hilit-chg-list
3ec30bcb 725 (list last-category last-face)))))
e287d328
RS
726
727(defun hilit-chg-bump-change (prop start end)
3ec30bcb 728 "Increment (age) the Highlight Changes mode text property."
e287d328
RS
729 (let ( new-prop )
730 (if (eq prop 'hilit-chg-delete)
731 (setq new-prop (nth 2 hilit-chg-list))
3ec30bcb 732 (setq new-prop (nth 2 (member prop hilit-chg-list))))
e287d328
RS
733 (if prop
734 (put-text-property start end 'hilit-chg new-prop)
3ec30bcb 735 (message "%d-%d unknown property %s not changed" start end prop))))
e287d328
RS
736
737;;;###autoload
738(defun highlight-changes-rotate-faces ()
7c655cf6 739 "Rotate the faces if in Highlight Changes mode and the changes are visible.
e287d328 740
3ec30bcb
GM
741Current changes are displayed in the face described by the first element
742of `highlight-changes-face-list', one level older changes are shown in
e287d328
RS
743face described by the second element, and so on. Very old changes remain
744shown in the last face in the list.
745
2c470dc3
JB
746You can automatically rotate colors when the buffer is saved by adding
747this function to `write-file-functions' as a buffer-local value. To do
748this, eval the following in the buffer to be saved:
3ec30bcb 749
2c470dc3 750 \(add-hook 'write-file-functions 'highlight-changes-rotate-faces nil t)"
e287d328 751 (interactive)
7c655cf6 752 (when (and highlight-changes-mode highlight-changes-visible-mode)
05d6ece7
CY
753 (let ((modified (buffer-modified-p))
754 (inhibit-modification-hooks t))
755 ;; The `modified' related code tries to combine two goals: (1) Record the
756 ;; rotation in `buffer-undo-list' and (2) avoid setting the modified flag
757 ;; of the current buffer due to the rotation. We do this by inserting (in
758 ;; `buffer-undo-list') entries restoring buffer-modified-p to nil before
759 ;; and after the entry for the rotation.
2e819508
SM
760 ;; FIXME: this is no good: we need to test the `modified' state at the
761 ;; time of the undo, not at the time of the "do", otherwise the undo
762 ;; may erroneously clear the modified flag. --Stef
763 ;; (unless modified
764 ;; ;; Install the "before" entry.
765 ;; (push '(apply restore-buffer-modified-p nil) buffer-undo-list))
05d6ece7
CY
766 (unwind-protect
767 (progn
768 ;; ensure hilit-chg-list is made and up to date
769 (hilit-chg-make-list)
770 ;; remove our existing overlays
771 (hilit-chg-hide-changes)
772 ;; for each change text property, increment it
773 (hilit-chg-map-changes 'hilit-chg-bump-change)
7c655cf6
SM
774 ;; and display them
775 (hilit-chg-display-changes))
05d6ece7 776 (unless modified
2e819508
SM
777 ;; Install the "after" entry. FIXME: See above.
778 ;; (push '(apply restore-buffer-modified-p nil) buffer-undo-list)
05d6ece7
CY
779
780 (restore-buffer-modified-p nil)))))
2c470dc3 781 ;; This always returns nil so it is safe to use in write-file-functions
e287d328
RS
782 nil)
783
e287d328 784;; ========================================================================
f826bf67
EZ
785;; Comparing buffers/files
786;; These use ediff to find the differences.
787
788(defun highlight-markup-buffers
789 (buf-a file-a buf-b file-b &optional markup-a-only)
790 "Get differences between two buffers and set highlight changes.
791Both buffers are done unless optional parameter MARKUP-A-ONLY
792is non-nil."
7c655cf6
SM
793 (eval-and-compile
794 (require 'ediff-util))
f826bf67
EZ
795 (save-window-excursion
796 (let* (change-info
797 change-a change-b
798 a-start a-end len-a
799 b-start b-end len-b
800 (bufa-modified (buffer-modified-p buf-a))
801 (bufb-modified (buffer-modified-p buf-b))
802 (buf-a-read-only (with-current-buffer buf-a buffer-read-only))
803 (buf-b-read-only (with-current-buffer buf-b buffer-read-only))
804 temp-a temp-b)
805 (if (and file-a bufa-modified)
806 (if (y-or-n-p (format "Save buffer %s? " buf-a))
807 (with-current-buffer buf-a
808 (save-buffer)
809 (setq bufa-modified (buffer-modified-p buf-a)))
810 (setq file-a nil)))
811 (or file-a
812 (setq temp-a (setq file-a (ediff-make-temp-file buf-a nil))))
813
814 (if (and file-b bufb-modified)
815 (if (y-or-n-p (format "Save buffer %s? " buf-b))
816 (with-current-buffer buf-b
817 (save-buffer)
818 (setq bufb-modified (buffer-modified-p buf-b)))
819 (setq file-b nil)))
820 (or file-b
821 (setq temp-b (setq file-b (ediff-make-temp-file buf-b nil))))
822 (set-buffer buf-a)
7c655cf6 823 (highlight-changes-mode 1)
f826bf67 824 (or markup-a-only (with-current-buffer buf-b
7c655cf6 825 (highlight-changes-mode 1)))
f826bf67
EZ
826 (setq change-info (hilit-chg-get-diff-info buf-a file-a buf-b file-b))
827
828
829 (setq change-a (car change-info))
830 (setq change-b (car (cdr change-info)))
85fcb671 831
f826bf67
EZ
832 (hilit-chg-make-list)
833 (while change-a
834 (setq a-start (nth 0 (car change-a)))
835 (setq a-end (nth 1 (car change-a)))
836 (setq b-start (nth 0 (car change-b)))
837 (setq b-end (nth 1 (car change-b)))
838 (setq len-a (- a-end a-start))
839 (setq len-b (- b-end b-start))
840 (set-buffer buf-a)
841 (hilit-chg-set-face-on-change a-start a-end len-b buf-a-read-only)
842 (or markup-a-only
843 (with-current-buffer buf-b
844 (hilit-chg-set-face-on-change b-start b-end len-a
845 buf-b-read-only)
846 ))
847 (setq change-a (cdr change-a))
848 (setq change-b (cdr change-b)))
849 (or bufa-modified
850 (with-current-buffer buf-a (set-buffer-modified-p nil)))
851 (or bufb-modified
852 (with-current-buffer buf-b (set-buffer-modified-p nil)))
853 (if temp-a
854 (delete-file temp-a))
855 (if temp-b
856 (delete-file temp-b)))
857 ))
858
859;;;###autoload
860(defun highlight-compare-buffers (buf-a buf-b)
861"Compare two buffers and highlight the differences.
862
863The default is the current buffer and the one in the next window.
864
865If either buffer is modified and is visiting a file, you are prompted
866to save the file.
867
2c470dc3 868Unless the buffer is unmodified and visiting a file, the buffer is
f826bf67
EZ
869written to a temporary file for comparison.
870
871If a buffer is read-only, differences will be highlighted but no property
872changes are made, so \\[highlight-changes-next-change] and
873\\[highlight-changes-previous-change] will not work."
874 (interactive
85fcb671 875 (list
f826bf67
EZ
876 (get-buffer (read-buffer "buffer-a " (current-buffer) t))
877 (get-buffer
878 (read-buffer "buffer-b "
85fcb671 879 (window-buffer (next-window (selected-window))) t))))
f826bf67
EZ
880 (let ((file-a (buffer-file-name buf-a))
881 (file-b (buffer-file-name buf-b)))
882 (highlight-markup-buffers buf-a file-a buf-b file-b)
883 ))
e287d328
RS
884
885;;;###autoload
b32e3ef8
KH
886(defun highlight-compare-with-file (file-b)
887 "Compare this buffer with a file, and highlight differences.
e287d328 888
3ec30bcb
GM
889If the buffer has a backup filename, it is used as the default when
890this function is called interactively.
e287d328 891
3ec30bcb
GM
892If the current buffer is visiting the file being compared against, it
893also will have its differences highlighted. Otherwise, the file is
894read in temporarily but the buffer is deleted.
e287d328 895
3ec30bcb
GM
896If the buffer is read-only, differences will be highlighted but no property
897changes are made, so \\[highlight-changes-next-change] and
e287d328 898\\[highlight-changes-previous-change] will not work."
7c655cf6
SM
899 (interactive
900 (let ((file buffer-file-name)
901 (file-name nil)
902 (file-dir nil))
903 (and file
904 (setq file-name (file-name-nondirectory file)
905 file-dir (file-name-directory file)))
906 (setq file-name (make-backup-file-name file-name))
907 (unless (file-exists-p file-name)
908 (setq file-name nil))
909 (list (read-file-name
910 "Find to compare with: " ;; prompt
911 file-dir ;; directory
912 nil ;; default
913 nil ;; existing
914 file-name) ;; initial
915 )))
e287d328 916 (let* ((buf-a (current-buffer))
e287d328
RS
917 (file-a (buffer-file-name))
918 (existing-buf (get-file-buffer file-b))
919 (buf-b (or existing-buf
06b60517 920 (find-file-noselect file-b))))
f826bf67
EZ
921 (highlight-markup-buffers buf-a file-a buf-b file-b (not existing-buf))
922 (unless existing-buf
923 (kill-buffer buf-b))
924 ))
e287d328
RS
925
926
927(defun hilit-chg-get-diff-info (buf-a file-a buf-b file-b)
849b02b4
GM
928 ;; hilit-e,x,y are set by function hilit-chg-get-diff-list-hk.
929 (let (hilit-e hilit-x hilit-y)
e287d328
RS
930 (ediff-setup buf-a file-a buf-b file-b
931 nil nil ; buf-c file-C
932 'hilit-chg-get-diff-list-hk
933 (list (cons 'ediff-job-name 'something))
934 )
849b02b4
GM
935 (ediff-with-current-buffer hilit-e (ediff-really-quit nil))
936 (list hilit-x hilit-y)))
e287d328
RS
937
938
939(defun hilit-chg-get-diff-list-hk ()
849b02b4
GM
940 ;; hilit-e/x/y are dynamically bound by hilit-chg-get-diff-info
941 ;; which calls this function as a hook.
942 (defvar hilit-x) ; placate the byte-compiler
943 (defvar hilit-y)
944 (defvar hilit-e)
945 (setq hilit-e (current-buffer))
e287d328 946 (let ((n 0) extent p va vb a b)
849b02b4 947 (setq hilit-x nil hilit-y nil)
e287d328
RS
948 (while (< n ediff-number-of-differences)
949 (ediff-make-fine-diffs n)
950 (setq va (ediff-get-fine-diff-vector n 'A))
951 ;; va is a vector if there are fine differences
952 (if va
953 (setq a (append va nil))
f6ec4635 954 ;; if not, get the unrefined difference
e287d328 955 (setq va (ediff-get-difference n 'A))
3ec30bcb 956 (setq a (list (elt va 0))))
e287d328
RS
957 ;; a list a list
958 (setq p a)
959 (while p
960 (setq extent (list (overlay-start (car p))
961 (overlay-end (car p))))
962 (setq p (cdr p))
849b02b4 963 (setq hilit-x (append hilit-x (list extent) )));; while p
e287d328
RS
964 ;;
965 (setq vb (ediff-get-fine-diff-vector n 'B))
966 ;; vb is a vector
967 (if vb
968 (setq b (append vb nil))
f6ec4635 969 ;; if not, get the unrefined difference
e287d328 970 (setq vb (ediff-get-difference n 'B))
3ec30bcb 971 (setq b (list (elt vb 0))))
e287d328
RS
972 ;; b list a list
973 (setq p b)
974 (while p
975 (setq extent (list (overlay-start (car p))
976 (overlay-end (car p))))
977 (setq p (cdr p))
849b02b4 978 (setq hilit-y (append hilit-y (list extent) )))
3ec30bcb 979 (setq n (1+ n)));; while
e287d328
RS
980 ;; ediff-quit doesn't work here.
981 ;; No point in returning a value, since this is a hook function.
982 ))
983
7c655cf6 984;; ======================= global-highlight-changes-mode ==============
e287d328 985
e287d328 986;;;###autoload
f5422e78 987(define-globalized-minor-mode global-highlight-changes-mode
7c655cf6 988 highlight-changes-mode highlight-changes-mode-turn-on)
e287d328 989
7c655cf6
SM
990(define-obsolete-function-alias
991 'global-highlight-changes
f5422e78 992 'global-highlight-changes-mode "23.1")
7c655cf6
SM
993
994(defun highlight-changes-mode-turn-on ()
f3b21763
JB
995 "See if Highlight Changes mode should be turned on for this buffer.
996This is called when `global-highlight-changes-mode' is turned on."
e287d328
RS
997 (or highlight-changes-mode ; do nothing if already on
998 (if
999 (cond
1000 ((null highlight-changes-global-modes)
1001 nil)
1002 ((functionp highlight-changes-global-modes)
1003 (funcall highlight-changes-global-modes))
1004 ((listp highlight-changes-global-modes)
1005 (if (eq (car-safe highlight-changes-global-modes) 'not)
1006 (not (memq major-mode (cdr highlight-changes-global-modes)))
1007 (memq major-mode highlight-changes-global-modes)))
1008 (t
71296446 1009 (and
ddd1e91e 1010 (not (string-match "^[ *]" (buffer-name)))
3ec30bcb 1011 (buffer-file-name))))
7c655cf6
SM
1012 (highlight-changes-mode 1))
1013 ))
71296446 1014
e287d328 1015
abe5c13a
LH
1016;;;; Desktop support.
1017
1018;; Called by `desktop-create-buffer' to restore `highlight-changes-mode'.
1019(defun hilit-chg-desktop-restore (desktop-buffer-locals)
1020 (highlight-changes-mode
1021 (or (cdr (assq 'highlight-changes-mode desktop-buffer-locals)) 1)))
1022
1023(add-to-list 'desktop-minor-mode-handlers
1024 '(highlight-changes-mode . hilit-chg-desktop-restore))
1025
9e722067
LH
1026(add-to-list 'desktop-locals-to-save 'highlight-changes-mode)
1027
e287d328
RS
1028;; ===================== debug ==================
1029;; For debug & test use:
1030;;
1031;; (defun hilit-chg-debug-show (&optional beg end)
1032;; (interactive)
1033;; (message "--- hilit-chg-debug-show ---")
4f91a816
SM
1034;; (hilit-chg-map-changes (lambda (prop start end)
1035;; (message "%d-%d: %s" start end prop))
e287d328
RS
1036;; beg end
1037;; ))
71296446 1038;;
e287d328
RS
1039;; ================== end of debug ===============
1040
e287d328 1041(provide 'hilit-chg)
e287d328 1042
fdbd749a 1043;;; hilit-chg.el ends here