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