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