xdisp.c: Fix typo in a comment in note_tool_bar_highlight.
[bpt/emacs.git] / lisp / vc / add-log.el
CommitLineData
5abdc915 1;;; add-log.el --- change log maintenance commands for Emacs
84fc2cfa 2
e91081eb 3;; Copyright (C) 1985, 1986, 1988, 1993, 1994, 1997, 1998, 2000, 2001,
114f9c96 4;; 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
c4f6e489 5;; Free Software Foundation, Inc.
84fc2cfa 6
6228c05b 7;; Maintainer: FSF
9766adfb 8;; Keywords: vc tools
e9571d2a 9
84fc2cfa
ER
10;; This file is part of GNU Emacs.
11
eb3fa2cf 12;; GNU Emacs is free software: you can redistribute it and/or modify
84fc2cfa 13;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
84fc2cfa
ER
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
eb3fa2cf 23;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
84fc2cfa 24
e41b2db1
ER
25;;; Commentary:
26
27;; This facility is documented in the Emacs Manual.
28
b0a96f7b
SM
29;; Todo:
30
31;; - Find/use/create _MTN/log if there's a _MTN directory.
32;; - Find/use/create ++log.* if there's an {arch} directory.
33;; - Use an open *VC-Log* or *cvs-commit* buffer if it's related to the
34;; source file.
35;; - Don't add TAB indents (and username?) if inserting entries in those
36;; special places.
37
fd7fa35a 38;;; Code:
84fc2cfa 39
776d8e16 40(eval-when-compile
5960adc7 41 (require 'timezone))
3697b807 42
fcad5199 43(defgroup change-log nil
7b297602 44 "Change log maintenance."
fcad5199 45 :group 'tools
3697b807 46 :link '(custom-manual "(emacs)Change Log")
fcad5199
RS
47 :prefix "change-log-"
48 :prefix "add-log-")
84fc2cfa 49
fcad5199
RS
50
51(defcustom change-log-default-name nil
b0a96f7b 52 "Name of a change log file for \\[add-change-log-entry]."
fcad5199
RS
53 :type '(choice (const :tag "default" nil)
54 string)
55 :group 'change-log)
dabff07c 56;;;###autoload
10b35c39 57(put 'change-log-default-name 'safe-local-variable 'string-or-null-p)
fcad5199 58
c3979f12
DL
59(defcustom change-log-mode-hook nil
60 "Normal hook run by `change-log-mode'."
61 :type 'hook
62 :group 'change-log)
63
c882e115
RS
64;; Many modes set this variable, so avoid warnings.
65;;;###autoload
fcad5199 66(defcustom add-log-current-defun-function nil
bb042dc6 67 "If non-nil, function to guess name of surrounding function.
5960adc7
DL
68It is used by `add-log-current-defun' in preference to built-in rules.
69Returns function's name as a string, or nil if outside a function."
cea3855a 70 :type '(choice (const nil) function)
fcad5199 71 :group 'change-log)
6258d3af 72
29db528b 73;;;###autoload
fcad5199 74(defcustom add-log-full-name nil
bb042dc6 75 "Full name of user, for inclusion in ChangeLog daily headers.
075a6629 76This defaults to the value returned by the function `user-full-name'."
fcad5199
RS
77 :type '(choice (const :tag "Default" nil)
78 string)
79 :group 'change-log)
02ec1592 80
29db528b 81;;;###autoload
fcad5199 82(defcustom add-log-mailing-address nil
d1921057 83 "Email addresses of user, for inclusion in ChangeLog headers.
c1b3ae42
CW
84This defaults to the value of `user-mail-address'. In addition to
85being a simple string, this value can also be a list. All elements
86will be recognized as referring to the same user; when creating a new
87ChangeLog entry, one element will be chosen at random."
fcad5199 88 :type '(choice (const :tag "Default" nil)
24f4201f
MR
89 (string :tag "String")
90 (repeat :tag "List of Strings" string))
fcad5199
RS
91 :group 'change-log)
92
df63ae66 93(defcustom add-log-time-format 'add-log-iso8601-time-string
d1921057 94 "Function that defines the time format.
df63ae66
RS
95For example, `add-log-iso8601-time-string', which gives the
96date in international ISO 8601 format,
97and `current-time-string' are two valid values."
98 :type '(radio (const :tag "International ISO 8601 format"
99 add-log-iso8601-time-string)
100 (const :tag "Old format, as returned by `current-time-string'"
101 current-time-string)
102 (function :tag "Other"))
103 :group 'change-log)
02ec1592 104
83afd62c 105(defcustom add-log-keep-changes-together nil
d1921057 106 "If non-nil, normally keep day's log entries for one file together.
3697b807
DL
107
108Log entries for a given file made with \\[add-change-log-entry] or
109\\[add-change-log-entry-other-window] will only be added to others \
110for that file made
111today if this variable is non-nil or that file comes first in today's
112entries. Otherwise another entry for that file will be started. An
113original log:
114
115 * foo (...): ...
116 * bar (...): change 1
83afd62c 117
3697b807
DL
118in the latter case, \\[add-change-log-entry-other-window] in a \
119buffer visiting `bar', yields:
83afd62c 120
3697b807
DL
121 * bar (...): -!-
122 * foo (...): ...
123 * bar (...): change 1
83afd62c 124
3697b807 125and in the former:
83afd62c 126
3697b807
DL
127 * foo (...): ...
128 * bar (...): change 1
129 (...): -!-
83afd62c 130
3697b807
DL
131The NEW-ENTRY arg to `add-change-log-entry' can override the effect of
132this variable."
133 :version "20.3"
83afd62c
KH
134 :type 'boolean
135 :group 'change-log)
136
598f34fa 137(defcustom add-log-always-start-new-record nil
d1921057 138 "If non-nil, `add-change-log-entry' will always start a new record."
bf247b6e 139 :version "22.1"
598f34fa
SS
140 :type 'boolean
141 :group 'change-log)
142
d68f7f1b 143(defcustom add-log-buffer-file-name-function nil
d1921057 144 "If non-nil, function to call to identify the full filename of a buffer.
d68f7f1b
SM
145This function is called with no argument. If this is nil, the default is to
146use `buffer-file-name'."
cea3855a 147 :type '(choice (const nil) function)
d68f7f1b
SM
148 :group 'change-log)
149
666f4056 150(defcustom add-log-file-name-function nil
d1921057 151 "If non-nil, function to call to identify the filename for a ChangeLog entry.
075a6629
DL
152This function is called with one argument, the value of variable
153`buffer-file-name' in that buffer. If this is nil, the default is to
154use the file's name relative to the directory of the change log file."
cea3855a 155 :type '(choice (const nil) function)
666f4056
RS
156 :group 'change-log)
157
776d8e16
GM
158
159(defcustom change-log-version-info-enabled nil
bb042dc6 160 "If non-nil, enable recording version numbers with the changes."
776d8e16
GM
161 :version "21.1"
162 :type 'boolean
163 :group 'change-log)
164
165(defcustom change-log-version-number-regexp-list
eb9c16e5 166 (let ((re "\\([0-9]+\.[0-9.]+\\)"))
776d8e16
GM
167 (list
168 ;; (defconst ad-version "2.15"
169 (concat "^(def[^ \t\n]+[ \t]+[^ \t\n][ \t]\"" re)
170 ;; Revision: pcl-cvs.el,v 1.72 1999/09/05 20:21:54 monnier Exp
5960adc7 171 (concat "^;+ *Revision: +[^ \t\n]+[ \t]+" re)))
bb042dc6 172 "List of regexps to search for version number.
5960adc7 173The version number must be in group 1.
776d8e16
GM
174Note: The search is conducted only within 10%, at the beginning of the file."
175 :version "21.1"
176 :type '(repeat regexp)
177 :group 'change-log)
178
fe735a8d 179(defface change-log-date
163f7b71
GM
180 '((t (:inherit font-lock-string-face)))
181 "Face used to highlight dates in date lines."
182 :version "21.1"
183 :group 'change-log)
c4f6e489 184(define-obsolete-face-alias 'change-log-date-face 'change-log-date "22.1")
163f7b71 185
fe735a8d 186(defface change-log-name
163f7b71
GM
187 '((t (:inherit font-lock-constant-face)))
188 "Face for highlighting author names."
189 :version "21.1"
190 :group 'change-log)
c4f6e489 191(define-obsolete-face-alias 'change-log-name-face 'change-log-name "22.1")
163f7b71 192
fe735a8d 193(defface change-log-email
163f7b71
GM
194 '((t (:inherit font-lock-variable-name-face)))
195 "Face for highlighting author email addresses."
196 :version "21.1"
197 :group 'change-log)
c4f6e489 198(define-obsolete-face-alias 'change-log-email-face 'change-log-email "22.1")
163f7b71 199
fe735a8d 200(defface change-log-file
163f7b71
GM
201 '((t (:inherit font-lock-function-name-face)))
202 "Face for highlighting file names."
203 :version "21.1"
204 :group 'change-log)
c4f6e489 205(define-obsolete-face-alias 'change-log-file-face 'change-log-file "22.1")
163f7b71 206
fe735a8d 207(defface change-log-list
163f7b71
GM
208 '((t (:inherit font-lock-keyword-face)))
209 "Face for highlighting parenthesized lists of functions or variables."
210 :version "21.1"
211 :group 'change-log)
c4f6e489 212(define-obsolete-face-alias 'change-log-list-face 'change-log-list "22.1")
598f34fa 213
fe735a8d 214(defface change-log-conditionals
163f7b71
GM
215 '((t (:inherit font-lock-variable-name-face)))
216 "Face for highlighting conditionals of the form `[...]'."
217 :version "21.1"
218 :group 'change-log)
c4f6e489
GM
219(define-obsolete-face-alias 'change-log-conditionals-face
220 'change-log-conditionals "22.1")
163f7b71 221
fe735a8d 222(defface change-log-function
163f7b71
GM
223 '((t (:inherit font-lock-variable-name-face)))
224 "Face for highlighting items of the form `<....>'."
225 :version "21.1"
226 :group 'change-log)
c4f6e489
GM
227(define-obsolete-face-alias 'change-log-function-face
228 'change-log-function "22.1")
163f7b71 229
fe735a8d 230(defface change-log-acknowledgement
163f7b71
GM
231 '((t (:inherit font-lock-comment-face)))
232 "Face for highlighting acknowledgments."
233 :version "21.1"
234 :group 'change-log)
c4f6e489
GM
235(define-obsolete-face-alias 'change-log-acknowledgement-face
236 'change-log-acknowledgement "22.1")
776d8e16 237
a28ed9e5 238(defconst change-log-file-names-re "^\\( +\\|\t\\)\\* \\([^ ,:([\n]+\\)")
000605b3 239(defconst change-log-start-entry-re "^\\sw.........[0-9:+ ]*")
a28ed9e5 240
5f562719 241(defvar change-log-font-lock-keywords
a28ed9e5 242 `(;;
080525ae
KH
243 ;; Date lines, new (2000-01-01) and old (Sat Jan 1 00:00:00 2000) styles.
244 ;; Fixme: this regepx is just an approximate one and may match
245 ;; wrongly with a non-date line existing as a random note. In
246 ;; addition, using any kind of fixed setting like this doesn't
247 ;; work if a user customizes add-log-time-format.
bf1e8d4a 248 ("^[0-9-]+ +\\|^ \\{11,\\}\\|^\\(Sun\\|Mon\\|Tue\\|Wed\\|Thu\\|Fri\\|Sat\\) [A-z][a-z][a-z] [0-9:+ ]+"
080525ae 249 (0 'change-log-date-face)
97a3278b 250 ;; Name and e-mail; some people put e-mail in parens, not angles.
7397a79f 251 ("\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]" nil nil
fe735a8d
MB
252 (1 'change-log-name)
253 (2 'change-log-email)))
5572c1d1
SM
254 ;;
255 ;; File names.
a28ed9e5 256 (,change-log-file-names-re
fe735a8d 257 (2 'change-log-file)
97a3278b 258 ;; Possibly further names in a list:
fe735a8d 259 ("\\=, \\([^ ,:([\n]+\\)" nil nil (1 'change-log-file))
97a3278b 260 ;; Possibly a parenthesized list of names:
171c707b 261 ("\\= (\\([^(),\n]+\\|(\\(setf\\|SETF\\) [^() ,\n]+)\\)"
fe735a8d 262 nil nil (1 'change-log-list))
171c707b 263 ("\\=, *\\([^(),\n]+\\|(\\(setf\\|SETF\\) [^() ,\n]+)\\)"
fe735a8d 264 nil nil (1 'change-log-list)))
5572c1d1
SM
265 ;;
266 ;; Function or variable names.
9e4b54a0 267 ("^\\( +\\|\t\\)(\\([^(),\n]+\\|(\\(setf\\|SETF\\) [^() ,\n]+)\\)"
fe735a8d 268 (2 'change-log-list)
171c707b 269 ("\\=, *\\([^(),\n]+\\|(\\(setf\\|SETF\\) [^() ,\n]+)\\)" nil nil
fe735a8d 270 (1 'change-log-list)))
5572c1d1
SM
271 ;;
272 ;; Conditionals.
fe735a8d 273 ("\\[!?\\([^]\n]+\\)\\]\\(:\\| (\\)" (1 'change-log-conditionals))
5572c1d1 274 ;;
a8d693d8 275 ;; Function of change.
fe735a8d 276 ("<\\([^>\n]+\\)>\\(:\\| (\\)" (1 'change-log-function))
a8d693d8 277 ;;
a3cab9f0 278 ;; Acknowledgements.
a8e10524
RS
279 ;; Don't include plain "From" because that is vague;
280 ;; we want to encourage people to say something more specific.
c50089c9
RS
281 ;; Note that the FSF does not use "Patches by"; our convention
282 ;; is to put the name of the author of the changes at the top
283 ;; of the change log entry.
9e4b54a0 284 ("\\(^\\( +\\|\t\\)\\| \\)\\(Patch\\(es\\)? by\\|Report\\(ed by\\| from\\)\\|Suggest\\(ed by\\|ion from\\)\\)"
fe735a8d 285 3 'change-log-acknowledgement))
5f562719
RS
286 "Additional expressions to highlight in Change Log mode.")
287
a28ed9e5
DN
288(defun change-log-search-file-name (where)
289 "Return the file-name for the change under point."
290 (save-excursion
291 (goto-char where)
292 (beginning-of-line 1)
000605b3
DN
293 (if (looking-at change-log-start-entry-re)
294 ;; We are at the start of an entry, search forward for a file
295 ;; name.
296 (progn
297 (re-search-forward change-log-file-names-re nil t)
f06b5ed2 298 (match-string-no-properties 2))
000605b3
DN
299 (if (looking-at change-log-file-names-re)
300 ;; We found a file name.
f06b5ed2 301 (match-string-no-properties 2)
000605b3
DN
302 ;; Look backwards for either a file name or the log entry start.
303 (if (re-search-backward
eb9c16e5 304 (concat "\\(" change-log-start-entry-re
000605b3
DN
305 "\\)\\|\\("
306 change-log-file-names-re "\\)") nil t)
307 (if (match-beginning 1)
308 ;; We got the start of the entry, look forward for a
309 ;; file name.
310 (progn
311 (re-search-forward change-log-file-names-re nil t)
f06b5ed2
MR
312 (match-string-no-properties 2))
313 (match-string-no-properties 4))
000605b3
DN
314 ;; We must be before any file name, look forward.
315 (re-search-forward change-log-file-names-re nil t)
f06b5ed2 316 (match-string-no-properties 2))))))
a28ed9e5
DN
317
318(defun change-log-find-file ()
319 "Visit the file for the change under point."
320 (interactive)
321 (let ((file (change-log-search-file-name (point))))
322 (if (and file (file-exists-p file))
323 (find-file file)
000605b3 324 (message "No such file or directory: %s" file))))
a28ed9e5 325
f06b5ed2
MR
326(defun change-log-search-tag-name-1 (&optional from)
327 "Search for a tag name within subexpression 1 of last match.
328Optional argument FROM specifies a buffer position where the tag
329name should be located. Return value is a cons whose car is the
330string representing the tag and whose cdr is the position where
331the tag was found."
332 (save-restriction
333 (narrow-to-region (match-beginning 1) (match-end 1))
334 (when from (goto-char from))
335 ;; The regexp below skips any symbol near `point' (FROM) followed by
336 ;; whitespace and another symbol. This should skip, for example,
337 ;; "struct" in a specification like "(struct buffer)" and move to
338 ;; "buffer". A leading paren is ignored.
339 (when (looking-at
340 "[(]?\\(?:\\(?:\\sw\\|\\s_\\)+\\(?:[ \t]+\\(\\sw\\|\\s_\\)+\\)\\)")
341 (goto-char (match-beginning 1)))
342 (cons (find-tag-default) (point))))
343
344(defconst change-log-tag-re
345 "(\\(\\(?:\\sw\\|\\s_\\)+\\(?:[, \t]+\\(?:\\sw\\|\\s_\\)+\\)*\\))"
346 "Regexp matching a tag name in change log entries.")
347
348(defun change-log-search-tag-name (&optional at)
349 "Search for a tag name near `point'.
eb9c16e5
JB
350Optional argument AT non-nil means search near buffer position AT.
351Return value is a cons whose car is the string representing
f06b5ed2
MR
352the tag and whose cdr is the position where the tag was found."
353 (save-excursion
354 (goto-char (setq at (or at (point))))
355 (save-restriction
356 (widen)
357 (or (condition-case nil
358 ;; Within parenthesized list?
359 (save-excursion
360 (backward-up-list)
361 (when (looking-at change-log-tag-re)
362 (change-log-search-tag-name-1 at)))
363 (error nil))
364 (condition-case nil
bc53d544 365 ;; Before parenthesized list on same line?
f06b5ed2
MR
366 (save-excursion
367 (when (and (skip-chars-forward " \t")
368 (looking-at change-log-tag-re))
369 (change-log-search-tag-name-1)))
370 (error nil))
371 (condition-case nil
bc53d544 372 ;; Near file name?
f06b5ed2
MR
373 (save-excursion
374 (when (and (progn
375 (beginning-of-line)
376 (looking-at change-log-file-names-re))
377 (goto-char (match-end 0))
378 (skip-syntax-forward " ")
379 (looking-at change-log-tag-re))
380 (change-log-search-tag-name-1)))
381 (error nil))
382 (condition-case nil
bc53d544
MR
383 ;; Anywhere else within current entry?
384 (let ((from
385 (save-excursion
386 (end-of-line)
387 (if (re-search-backward change-log-start-entry-re nil t)
388 (match-beginning 0)
389 (point-min))))
390 (to
391 (save-excursion
392 (end-of-line)
393 (if (re-search-forward change-log-start-entry-re nil t)
394 (match-beginning 0)
395 (point-max)))))
396 (when (and (< from to) (<= from at) (<= at to))
397 (save-restriction
398 ;; Narrow to current change log entry.
399 (narrow-to-region from to)
400 (cond
401 ((re-search-backward change-log-tag-re nil t)
402 (narrow-to-region (match-beginning 1) (match-end 1))
403 (goto-char (point-max))
404 (cons (find-tag-default) (point-max)))
405 ((re-search-forward change-log-tag-re nil t)
406 (narrow-to-region (match-beginning 1) (match-end 1))
407 (goto-char (point-min))
408 (cons (find-tag-default) (point-min)))))))
f06b5ed2
MR
409 (error nil))))))
410
411(defvar change-log-find-head nil)
412(defvar change-log-find-tail nil)
9360906a 413(defvar change-log-find-window nil)
f06b5ed2
MR
414
415(defun change-log-goto-source-1 (tag regexp file buffer
416 &optional window first last)
417 "Search for tag TAG in buffer BUFFER visiting file FILE.
418REGEXP is a regular expression for TAG. The remaining arguments
419are optional: WINDOW denotes the window to display the results of
420the search. FIRST is a position in BUFFER denoting the first
421match from previous searches for TAG. LAST is the position in
422BUFFER denoting the last match for TAG in the last search."
423 (with-current-buffer buffer
424 (save-excursion
425 (save-restriction
426 (widen)
427 (if last
428 (progn
429 ;; When LAST is set make sure we continue from the next
430 ;; line end to not find the same tag again.
431 (goto-char last)
432 (end-of-line)
433 (condition-case nil
434 ;; Try to go to the end of the current defun to avoid
435 ;; false positives within the current defun's body
436 ;; since these would match `add-log-current-defun'.
437 (end-of-defun)
438 ;; Don't fall behind when `end-of-defun' fails.
439 (error (progn (goto-char last) (end-of-line))))
440 (setq last nil))
441 ;; When LAST was not set start at beginning of BUFFER.
442 (goto-char (point-min)))
443 (let (current-defun)
444 (while (and (not last) (re-search-forward regexp nil t))
445 ;; Verify that `add-log-current-defun' invoked at the end
446 ;; of the match returns TAG. This heuristic works well
447 ;; whenever the name of the defun occurs within the first
448 ;; line of the defun.
449 (setq current-defun (add-log-current-defun))
450 (when (and current-defun (string-equal current-defun tag))
451 ;; Record this as last match.
452 (setq last (line-beginning-position))
453 ;; Record this as first match when there's none.
454 (unless first (setq first last)))))))
455 (if (or last first)
9360906a
MR
456 (with-selected-window
457 (setq change-log-find-window (or window (display-buffer buffer)))
f06b5ed2
MR
458 (if last
459 (progn
460 (when (or (< last (point-min)) (> last (point-max)))
461 ;; Widen to show TAG.
462 (widen))
463 (push-mark)
464 (goto-char last))
465 ;; When there are no more matches go (back) to FIRST.
466 (message "No more matches for tag `%s' in file `%s'" tag file)
467 (setq last first)
468 (goto-char first))
469 ;; Return new "tail".
470 (list (selected-window) first last))
471 (message "Source location of tag `%s' not found in file `%s'" tag file)
472 nil)))
473
474(defun change-log-goto-source ()
bc53d544 475 "Go to source location of \"change log tag\" near `point'.
f06b5ed2 476A change log tag is a symbol within a parenthesized,
bc53d544
MR
477comma-separated list. If no suitable tag can be found nearby,
478try to visit the file for the change under `point' instead."
f06b5ed2
MR
479 (interactive)
480 (if (and (eq last-command 'change-log-goto-source)
481 change-log-find-tail)
482 (setq change-log-find-tail
483 (condition-case nil
484 (apply 'change-log-goto-source-1
485 (append change-log-find-head change-log-find-tail))
486 (error
487 (format "Cannot find more matches for tag `%s' in file `%s'"
488 (car change-log-find-head)
489 (nth 2 change-log-find-head)))))
490 (save-excursion
bc53d544
MR
491 (let* ((at (point))
492 (tag-at (change-log-search-tag-name))
f06b5ed2 493 (tag (car tag-at))
bc53d544
MR
494 (file (when tag-at (change-log-search-file-name (cdr tag-at))))
495 (file-at (when file (match-beginning 2)))
496 ;; `file-2' is the file `change-log-search-file-name' finds
497 ;; at `point'. We use `file-2' as a fallback when `tag' or
498 ;; `file' are not suitable for some reason.
499 (file-2 (change-log-search-file-name at))
500 (file-2-at (when file-2 (match-beginning 2))))
501 (cond
502 ((and (or (not tag) (not file) (not (file-exists-p file)))
503 (or (not file-2) (not (file-exists-p file-2))))
504 (error "Cannot find tag or file near `point'"))
505 ((and file-2 (file-exists-p file-2)
506 (or (not tag) (not file) (not (file-exists-p file))
507 (and (or (and (< file-at file-2-at) (<= file-2-at at))
508 (and (<= at file-2-at) (< file-2-at file-at))))))
509 ;; We either have not found a suitable file name or `file-2'
510 ;; provides a "better" file name wrt `point'. Go to the
511 ;; buffer of `file-2' instead.
9360906a
MR
512 (setq change-log-find-window
513 (display-buffer (find-file-noselect file-2))))
bc53d544 514 (t
f06b5ed2
MR
515 (setq change-log-find-head
516 (list tag (concat "\\_<" (regexp-quote tag) "\\_>")
517 file (find-file-noselect file)))
518 (condition-case nil
519 (setq change-log-find-tail
520 (apply 'change-log-goto-source-1 change-log-find-head))
bc53d544
MR
521 (error
522 (format "Cannot find matches for tag `%s' in file `%s'"
523 tag file)))))))))
f06b5ed2 524
7be2b094 525(defun change-log-next-error (&optional argp reset)
9360906a 526 "Move to the Nth (default 1) next match in a ChangeLog buffer.
7be2b094
TZ
527Compatibility function for \\[next-error] invocations."
528 (interactive "p")
529 (let* ((argp (or argp 0))
530 (count (abs argp)) ; how many cycles
531 (down (< argp 0)) ; are we going down? (is argp negative?)
532 (up (not down))
533 (search-function (if up 're-search-forward 're-search-backward)))
bc53d544 534
7be2b094
TZ
535 ;; set the starting position
536 (goto-char (cond (reset (point-min))
537 (down (line-beginning-position))
538 (up (line-end-position))
539 ((point))))
bc53d544 540
7be2b094 541 (funcall search-function change-log-file-names-re nil t count))
bc53d544 542
7be2b094
TZ
543 (beginning-of-line)
544 ;; if we found a place to visit...
545 (when (looking-at change-log-file-names-re)
9360906a
MR
546 (let (change-log-find-window)
547 (change-log-goto-source)
548 (when change-log-find-window
549 ;; Select window displaying source file.
550 (select-window change-log-find-window)))))
7be2b094 551
3066d4ad 552(defvar change-log-mode-map
d43c2b06
DN
553 (let ((map (make-sparse-keymap))
554 (menu-map (make-sparse-keymap)))
3066d4ad
SM
555 (define-key map [?\C-c ?\C-p] 'add-log-edit-prev-comment)
556 (define-key map [?\C-c ?\C-n] 'add-log-edit-next-comment)
a28ed9e5 557 (define-key map [?\C-c ?\C-f] 'change-log-find-file)
f06b5ed2 558 (define-key map [?\C-c ?\C-c] 'change-log-goto-source)
d43c2b06
DN
559 (define-key map [menu-bar changelog] (cons "ChangeLog" menu-map))
560 (define-key menu-map [gs]
561 '(menu-item "Go To Source" change-log-goto-source
562 :help "Go to source location of ChangeLog tag near point"))
563 (define-key menu-map [ff]
564 '(menu-item "Find File" change-log-find-file
565 :help "Visit the file for the change under point"))
566 (define-key menu-map [sep] '("--"))
567 (define-key menu-map [nx]
568 '(menu-item "Next Log-Edit Comment" add-log-edit-next-comment
569 :help "Cycle forward through Log-Edit mode comment history"))
570 (define-key menu-map [pr]
571 '(menu-item "Previous Log-Edit Comment" add-log-edit-prev-comment
572 :help "Cycle backward through Log-Edit mode comment history"))
3066d4ad 573 map)
45c50c5d 574 "Keymap for Change Log major mode.")
45c50c5d 575
d1921057
SM
576;; It used to be called change-log-time-zone-rule but really should be
577;; called add-log-time-zone-rule since it's only used from add-log-* code.
578(defvaralias 'change-log-time-zone-rule 'add-log-time-zone-rule)
579(defvar add-log-time-zone-rule nil
51bd1843
EN
580 "Time zone used for calculating change log time stamps.
581It takes the same format as the TZ argument of `set-time-zone-rule'.
d1921057
SM
582If nil, use local time.
583If t, use universal time.")
d52c204b
RS
584(put 'add-log-time-zone-rule 'safe-local-variable
585 '(lambda (x) (or (booleanp x) (stringp x))))
51bd1843 586
0739a962 587(defun add-log-iso8601-time-zone (&optional time)
51bd1843
EN
588 (let* ((utc-offset (or (car (current-time-zone time)) 0))
589 (sign (if (< utc-offset 0) ?- ?+))
590 (sec (abs utc-offset))
591 (ss (% sec 60))
592 (min (/ sec 60))
593 (mm (% min 60))
594 (hh (/ min 60)))
595 (format (cond ((not (zerop ss)) "%c%02d:%02d:%02d")
596 ((not (zerop mm)) "%c%02d:%02d")
597 (t "%c%02d"))
598 sign hh mm ss)))
599
d1921057
SM
600(defvar add-log-iso8601-with-time-zone nil)
601
df63ae66 602(defun add-log-iso8601-time-string ()
0739a962
SM
603 (let ((time (format-time-string "%Y-%m-%d"
604 nil (eq t add-log-time-zone-rule))))
d1921057
SM
605 (if add-log-iso8601-with-time-zone
606 (concat time " " (add-log-iso8601-time-zone))
607 time)))
df63ae66 608
84fc2cfa 609(defun change-log-name ()
075a6629 610 "Return (system-dependent) default name for a change log file."
84fc2cfa 611 (or change-log-default-name
7c2fb837 612 "ChangeLog"))
84fc2cfa 613
3066d4ad
SM
614(defun add-log-edit-prev-comment (arg)
615 "Cycle backward through Log-Edit mode comment history.
616With a numeric prefix ARG, go back ARG comments."
617 (interactive "*p")
618 (save-restriction
619 (narrow-to-region (point)
620 (if (memq last-command '(add-log-edit-prev-comment
621 add-log-edit-next-comment))
622 (mark) (point)))
623 (when (fboundp 'log-edit-previous-comment)
624 (log-edit-previous-comment arg)
625 (indent-region (point-min) (point-max))
626 (goto-char (point-min))
627 (unless (save-restriction (widen) (bolp))
628 (delete-region (point) (progn (skip-chars-forward " \t\n") (point))))
629 (set-mark (point-min))
630 (goto-char (point-max))
631 (delete-region (point) (progn (skip-chars-backward " \t\n") (point))))))
632
633(defun add-log-edit-next-comment (arg)
634 "Cycle forward through Log-Edit mode comment history.
635With a numeric prefix ARG, go back ARG comments."
636 (interactive "*p")
637 (add-log-edit-prev-comment (- arg)))
638
287d149f 639;;;###autoload
84fc2cfa
ER
640(defun prompt-for-change-log-name ()
641 "Prompt for a change log name."
117aaf60
KH
642 (let* ((default (change-log-name))
643 (name (expand-file-name
644 (read-file-name (format "Log file (default %s): " default)
645 nil default))))
646 ;; Handle something that is syntactically a directory name.
647 ;; Look for ChangeLog or whatever in that directory.
648 (if (string= (file-name-nondirectory name) "")
649 (expand-file-name (file-name-nondirectory default)
650 name)
651 ;; Handle specifying a file that is a directory.
652 (if (file-directory-p name)
653 (expand-file-name (file-name-nondirectory default)
654 (file-name-as-directory name))
655 name))))
84fc2cfa 656
776d8e16 657(defun change-log-version-number-search ()
5960adc7 658 "Return version number of current buffer's file.
ac3f4c6f 659This is the value returned by `vc-working-revision' or, if that is
5960adc7 660nil, by matching `change-log-version-number-regexp-list'."
776d8e16 661 (let* ((size (buffer-size))
fc9b0554 662 (limit
5960adc7
DL
663 ;; The version number can be anywhere in the file, but
664 ;; restrict search to the file beginning: 10% should be
665 ;; enough to prevent some mishits.
776d8e16 666 ;;
5960adc7
DL
667 ;; Apply percentage only if buffer size is bigger than
668 ;; approx 100 lines.
fc9b0554 669 (if (> size (* 100 80)) (+ (point) (/ size 10)))))
ac3f4c6f 670 (or (and buffer-file-name (vc-working-revision buffer-file-name))
5960adc7
DL
671 (save-restriction
672 (widen)
fc9b0554
SM
673 (let ((regexps change-log-version-number-regexp-list)
674 version)
5960adc7
DL
675 (while regexps
676 (save-excursion
677 (goto-char (point-min))
fc9b0554 678 (when (re-search-forward (pop regexps) limit t)
5960adc7 679 (setq version (match-string 1)
fc9b0554
SM
680 regexps nil))))
681 version)))))
776d8e16 682
89631590 683(declare-function diff-find-source-location "diff-mode"
62d93502 684 (&optional other-file reverse noprompt))
776d8e16 685
45a13f0d 686;;;###autoload
d68f7f1b 687(defun find-change-log (&optional file-name buffer-file)
45a13f0d 688 "Find a change log file for \\[add-change-log-entry] and return the name.
a82e2ed5
RS
689
690Optional arg FILE-NAME specifies the file to use.
de98fcaf 691If FILE-NAME is nil, use the value of `change-log-default-name'.
218cf475 692If `change-log-default-name' is nil, behave as though it were 'ChangeLog'
de98fcaf
RS
693\(or whatever we use on this operating system).
694
218cf475 695If `change-log-default-name' contains a leading directory component, then
513063cf 696simply find it in the current directory. Otherwise, search in the current
de98fcaf 697directory and its successive parents for a file so named.
45a13f0d
RM
698
699Once a file is found, `change-log-default-name' is set locally in the
d68f7f1b
SM
700current buffer to the complete file name.
701Optional arg BUFFER-FILE overrides `buffer-file-name'."
89631590
GM
702 ;; If we are called from a diff, first switch to the source buffer;
703 ;; in order to respect buffer-local settings of change-log-default-name, etc.
67a925e5
GM
704 (with-current-buffer (let ((buff (if (eq major-mode 'diff-mode)
705 (car (ignore-errors
706 (diff-find-source-location))))))
707 (if (buffer-live-p buff) buff
708 (current-buffer)))
89631590
GM
709 ;; If user specified a file name or if this buffer knows which one to use,
710 ;; just use that.
67a925e5
GM
711 (or file-name
712 (setq file-name (and change-log-default-name
713 (file-name-directory change-log-default-name)
714 change-log-default-name))
715 (progn
716 ;; Chase links in the source file
717 ;; and use the change log in the dir where it points.
718 (setq file-name (or (and (or buffer-file buffer-file-name)
a82e2ed5 719 (file-name-directory
67a925e5
GM
720 (file-chase-links
721 (or buffer-file buffer-file-name))))
722 default-directory))
723 (if (file-directory-p file-name)
724 (setq file-name (expand-file-name (change-log-name) file-name)))
725 ;; Chase links before visiting the file.
726 ;; This makes it easier to use a single change log file
727 ;; for several related directories.
728 (setq file-name (file-chase-links file-name))
729 (setq file-name (expand-file-name file-name))
730 ;; Move up in the dir hierarchy till we find a change log file.
731 (let ((file1 file-name)
732 parent-dir)
733 (while (and (not (or (get-file-buffer file1) (file-exists-p file1)))
734 (progn (setq parent-dir
735 (file-name-directory
736 (directory-file-name
737 (file-name-directory file1))))
738 ;; Give up if we are already at the root dir.
739 (not (string= (file-name-directory file1)
740 parent-dir))))
741 ;; Move up to the parent dir and try again.
742 (setq file1 (expand-file-name
743 (file-name-nondirectory (change-log-name))
744 parent-dir)))
745 ;; If we found a change log in a parent, use that.
746 (if (or (get-file-buffer file1) (file-exists-p file1))
747 (setq file-name file1)))))
748 ;; Make a local variable in this buffer so we needn't search again.
749 (set (make-local-variable 'change-log-default-name) file-name))
a82e2ed5 750 file-name)
45a13f0d 751
2eb7ccf4
SM
752(defun add-log-file-name (buffer-file log-file)
753 ;; Never want to add a change log entry for the ChangeLog file itself.
754 (unless (or (null buffer-file) (string= buffer-file log-file))
d68f7f1b
SM
755 (if add-log-file-name-function
756 (funcall add-log-file-name-function buffer-file)
757 (setq buffer-file
e1f5b0ed 758 (file-relative-name buffer-file (file-name-directory log-file)))
d68f7f1b
SM
759 ;; If we have a backup file, it's presumably because we're
760 ;; comparing old and new versions (e.g. for deleted
761 ;; functions) and we'll want to use the original name.
762 (if (backup-file-name-p buffer-file)
763 (file-name-sans-versions buffer-file)
764 buffer-file))))
2eb7ccf4 765
84fc2cfa 766;;;###autoload
2a520399
DN
767(defun add-change-log-entry (&optional whoami file-name other-window new-entry
768 put-new-entry-on-new-line)
d882f144 769 "Find change log file, and add an entry for today and an item for this file.
83afd62c 770Optional arg WHOAMI (interactive prefix) non-nil means prompt for user
d9ee5172 771name and email (stored in `add-log-full-name' and `add-log-mailing-address').
83afd62c 772
d882f144
RS
773Second arg FILE-NAME is file name of the change log.
774If nil, use the value of `change-log-default-name'.
775
287d149f 776Third arg OTHER-WINDOW non-nil means visit in other window.
d882f144 777
287d149f 778Fourth arg NEW-ENTRY non-nil means always create a new entry at the front;
3697b807
DL
779never append to an existing entry. Option `add-log-keep-changes-together'
780otherwise affects whether a new entry is created.
781
2a520399
DN
782Fifth arg PUT-NEW-ENTRY-ON-NEW-LINE non-nil means that if a new
783entry is created, put it on a new line by itself, do not put it
784after a comma on an existing line.
785
598f34fa
SS
786Option `add-log-always-start-new-record' non-nil means always create a
787new record, even when the last record was made on the same date and by
788the same person.
789
d882f144
RS
790The change log file can start with a copyright notice and a copying
791permission notice. The first blank line indicates the end of these
792notices.
793
d1921057 794Today's date is calculated according to `add-log-time-zone-rule' if
3697b807 795non-nil, otherwise in local time."
84fc2cfa
ER
796 (interactive (list current-prefix-arg
797 (prompt-for-change-log-name)))
d68f7f1b
SM
798 (let* ((defun (add-log-current-defun))
799 (version (and change-log-version-info-enabled
800 (change-log-version-number-search)))
8f530b95
SM
801 (buf-file-name (if add-log-buffer-file-name-function
802 (funcall add-log-buffer-file-name-function)
803 buffer-file-name))
804 (buffer-file (if buf-file-name (expand-file-name buf-file-name)))
dd816e0d 805 (file-name (expand-file-name (find-change-log file-name buffer-file)))
d882f144 806 ;; Set ITEM to the file name to use in the new item.
ad4573c7 807 (item (add-log-file-name buffer-file file-name)))
2eb7ccf4 808
7cd017ba 809 (unless (equal file-name buffer-file-name)
d499c5b9
SM
810 (cond
811 ((equal file-name (buffer-file-name (window-buffer (selected-window))))
812 ;; If the selected window already shows the desired buffer don't show
813 ;; it again (particularly important if other-window is true).
814 ;; This is important for diff-add-change-log-entries-other-window.
815 (set-buffer (window-buffer (selected-window))))
816 ((or other-window (window-dedicated-p (selected-window)))
817 (find-file-other-window file-name))
818 (t (find-file file-name))))
bb042dc6 819 (or (derived-mode-p 'change-log-mode)
675a998f 820 (change-log-mode))
84fc2cfa
ER
821 (undo-boundary)
822 (goto-char (point-min))
d882f144 823
ad4573c7
SM
824 (let ((full-name (or add-log-full-name (user-full-name)))
825 (mailing-address (or add-log-mailing-address user-mail-address)))
826
827 (when whoami
1e899515 828 (setq full-name (read-string "Full name: " full-name))
ad4573c7
SM
829 ;; Note that some sites have room and phone number fields in
830 ;; full name which look silly when inserted. Rather than do
831 ;; anything about that here, let user give prefix argument so that
832 ;; s/he can edit the full name field in prompter if s/he wants.
833 (setq mailing-address
1e899515 834 (read-string "Mailing address: " mailing-address)))
ad4573c7
SM
835
836 ;; If file starts with a copyright and permission notice, skip them.
837 ;; Assume they end at first blank line.
838 (when (looking-at "Copyright")
839 (search-forward "\n\n")
840 (skip-chars-forward "\n"))
841
842 ;; Advance into first entry if it is usable; else make new one.
843 (let ((new-entries
844 (mapcar (lambda (addr)
845 (concat
846 (if (stringp add-log-time-zone-rule)
847 (let ((tz (getenv "TZ")))
848 (unwind-protect
849 (progn
850 (set-time-zone-rule add-log-time-zone-rule)
851 (funcall add-log-time-format))
852 (set-time-zone-rule tz)))
853 (funcall add-log-time-format))
854 " " full-name
855 " <" addr ">"))
856 (if (consp mailing-address)
857 mailing-address
858 (list mailing-address)))))
859 (if (and (not add-log-always-start-new-record)
860 (let ((hit nil))
861 (dolist (entry new-entries hit)
862 (when (looking-at (regexp-quote entry))
863 (setq hit t)))))
864 (forward-line 1)
865 (insert (nth (random (length new-entries))
866 new-entries)
867 (if use-hard-newlines hard-newline "\n")
868 (if use-hard-newlines hard-newline "\n"))
869 (forward-line -1))))
82f4acaf 870
d882f144
RS
871 ;; Determine where we should stop searching for a usable
872 ;; item to add to, within this entry.
ad4573c7
SM
873 (let ((bound
874 (save-excursion
875 (if (looking-at "\n*[^\n* \t]")
876 (skip-chars-forward "\n")
877 (if add-log-keep-changes-together
878 (forward-page) ; page delimits entries for date
879 (forward-paragraph))) ; paragraph delimits entries for file
880 (point))))
881
882 ;; Now insert the new line for this item.
883 (cond ((re-search-forward "^\\s *\\*\\s *$" bound t)
884 ;; Put this file name into the existing empty item.
885 (if item
886 (insert item)))
887 ((and (not new-entry)
888 (let (case-fold-search)
889 (re-search-forward
890 (concat (regexp-quote (concat "* " item))
891 ;; Don't accept `foo.bar' when
892 ;; looking for `foo':
893 "\\(\\s \\|[(),:]\\)")
894 bound t)))
895 ;; Add to the existing item for the same file.
896 (re-search-forward "^\\s *$\\|^\\s \\*")
897 (goto-char (match-beginning 0))
898 ;; Delete excess empty lines; make just 2.
899 (while (and (not (eobp)) (looking-at "^\\s *$"))
900 (delete-region (point) (line-beginning-position 2)))
901 (insert (if use-hard-newlines hard-newline "\n")
902 (if use-hard-newlines hard-newline "\n"))
903 (forward-line -2)
904 (indent-relative-maybe))
905 (t
906 ;; Make a new item.
907 (while (looking-at "\\sW")
908 (forward-line 1))
909 (while (and (not (eobp)) (looking-at "^\\s *$"))
910 (delete-region (point) (line-beginning-position 2)))
911 (insert (if use-hard-newlines hard-newline "\n")
912 (if use-hard-newlines hard-newline "\n")
913 (if use-hard-newlines hard-newline "\n"))
914 (forward-line -2)
915 (indent-to left-margin)
916 (insert "* ")
917 (if item (insert item)))))
1832dbd1 918 ;; Now insert the function name, if we have one.
d882f144 919 ;; Point is at the item for this file,
21d7e080 920 ;; either at the end of the line or at the first blank line.
5a9ac14b
SM
921 (if (not defun)
922 ;; No function name, so put in a colon unless we have just a star.
923 (unless (save-excursion
924 (beginning-of-line 1)
925 (looking-at "\\s *\\(\\*\\s *\\)?$"))
926 (insert ": ")
7b297602 927 (if version (insert version ?\s)))
5a9ac14b
SM
928 ;; Make it easy to get rid of the function name.
929 (undo-boundary)
5960adc7
DL
930 (unless (save-excursion
931 (beginning-of-line 1)
5a9ac14b 932 (looking-at "\\s *$"))
7b297602 933 (insert ?\s))
5a9ac14b
SM
934 ;; See if the prev function name has a message yet or not.
935 ;; If not, merge the two items.
936 (let ((pos (point-marker)))
937 (skip-syntax-backward " ")
938 (skip-chars-backward "):")
2a520399
DN
939 (if (and (not put-new-entry-on-new-line)
940 (looking-at "):")
fc9b0554
SM
941 (let ((pos (save-excursion (backward-sexp 1) (point))))
942 (when (equal (buffer-substring pos (point)) defun)
943 (delete-region pos (point)))
944 (> fill-column (+ (current-column) (length defun) 4))))
945 (progn (skip-chars-backward ", ")
946 (delete-region (point) pos)
947 (unless (memq (char-before) '(?\()) (insert ", ")))
2a520399
DN
948 (when (and (not put-new-entry-on-new-line) (looking-at "):"))
949 (delete-region (+ 1 (point)) (line-end-position)))
5a9ac14b
SM
950 (goto-char pos)
951 (insert "("))
952 (set-marker pos nil))
953 (insert defun "): ")
7b297602 954 (if version (insert version ?\s)))))
84fc2cfa 955
84fc2cfa
ER
956;;;###autoload
957(defun add-change-log-entry-other-window (&optional whoami file-name)
d882f144
RS
958 "Find change log file in other window and add entry and item.
959This is just like `add-change-log-entry' except that it displays
960the change log file in another window."
84fc2cfa
ER
961 (interactive (if current-prefix-arg
962 (list current-prefix-arg
963 (prompt-for-change-log-name))))
964 (add-change-log-entry whoami file-name t))
965
6dbf6147 966
d1921057 967(defvar change-log-indent-text 0)
fc9b0554 968
6dbf6147
MR
969(defun change-log-fill-parenthesized-list ()
970 ;; Fill parenthesized lists of names according to GNU standards.
971 ;; * file-name.ext (very-long-foo, very-long-bar, very-long-foobar):
972 ;; should be filled as
973 ;; * file-name.ext (very-long-foo, very-long-bar)
974 ;; (very-long-foobar):
975 (save-excursion
976 (end-of-line 0)
977 (skip-chars-backward " \t")
978 (when (and (equal (char-before) ?\,)
979 (> (point) (1+ (point-min))))
980 (condition-case nil
981 (when (save-excursion
982 (and (prog2
983 (up-list -1)
984 (equal (char-after) ?\()
985 (skip-chars-backward " \t"))
986 (or (bolp)
987 ;; Skip everything but a whitespace or asterisk.
988 (and (not (zerop (skip-chars-backward "^ \t\n*")))
989 (skip-chars-backward " \t")
990 ;; We want one asterisk here.
991 (= (skip-chars-backward "*") -1)
992 (skip-chars-backward " \t")
993 (bolp)))))
994 ;; Delete the comma.
995 (delete-char -1)
996 ;; Close list on previous line.
997 (insert ")")
998 (skip-chars-forward " \t\n")
999 ;; Start list on new line.
1000 (insert-before-markers "("))
1001 (error nil)))))
1002
d1921057 1003(defun change-log-indent ()
6dbf6147 1004 (change-log-fill-parenthesized-list)
fc9b0554
SM
1005 (let* ((indent
1006 (save-excursion
1007 (beginning-of-line)
1008 (skip-chars-forward " \t")
1009 (cond
d1921057 1010 ((and (looking-at "\\(.*\\) [^ \n].*[^ \n] <.*>\\(?: +(.*)\\)? *$")
fc9b0554
SM
1011 ;; Matching the output of add-log-time-format is difficult,
1012 ;; but I'll get it has at least two adjacent digits.
1013 (string-match "[[:digit:]][[:digit:]]" (match-string 1)))
1014 0)
1015 ((looking-at "[^*(]")
d1921057 1016 (+ (current-left-margin) change-log-indent-text))
fc9b0554
SM
1017 (t (current-left-margin)))))
1018 (pos (save-excursion (indent-line-to indent) (point))))
1019 (if (> pos (point)) (goto-char pos))))
1020
1021
3066d4ad 1022(defvar smerge-resolve-function)
8bb4ed88 1023(defvar copyright-at-end-flag)
3066d4ad 1024
1da56800 1025;;;###autoload
7cd017ba 1026(define-derived-mode change-log-mode text-mode "Change Log"
eb9c16e5 1027 "Major mode for editing change logs; like Indented Text mode.
09b389d0 1028Prevents numeric backups and sets `left-margin' to 8 and `fill-column' to 74.
3697b807 1029New log entries are usually made with \\[add-change-log-entry] or \\[add-change-log-entry-other-window].
5516387c 1030Each entry behaves as a paragraph, and the entries for one day as a page.
3066d4ad 1031Runs `change-log-mode-hook'.
eb9c16e5 1032\n\\{change-log-mode-map}"
7cd017ba 1033 (setq left-margin 8
4f675a8c 1034 fill-column 74
60f10a06 1035 indent-tabs-mode t
ccf0d2ca
JB
1036 tab-width 8
1037 show-trailing-whitespace t)
905b7d38
SM
1038 (set (make-local-variable 'fill-forward-paragraph-function)
1039 'change-log-fill-forward-paragraph)
1040 ;; Make sure we call `change-log-indent' when filling.
1041 (set (make-local-variable 'fill-indent-according-to-mode) t)
3ee9a09c
MR
1042 ;; Avoid that filling leaves behind a single "*" on a line.
1043 (add-hook 'fill-nobreak-predicate
1044 '(lambda ()
8bb4ed88 1045 (looking-back "^\\s *\\*\\s *" (line-beginning-position)))
3ee9a09c 1046 nil t)
d1921057 1047 (set (make-local-variable 'indent-line-function) 'change-log-indent)
fc9b0554 1048 (set (make-local-variable 'tab-always-indent) nil)
8bb4ed88 1049 (set (make-local-variable 'copyright-at-end-flag) t)
4b7d4d0d
DL
1050 ;; We really do want "^" in paragraph-start below: it is only the
1051 ;; lines that begin at column 0 (despite the left-margin of 8) that
1052 ;; we are looking for. Adding `* ' allows eliding the blank line
1053 ;; between entries for different files.
c9cfb0f2 1054 (set (make-local-variable 'paragraph-start) "\\s *$\\|\f\\|^\\<")
4b7d4d0d 1055 (set (make-local-variable 'paragraph-separate) paragraph-start)
2c91c85c
RS
1056 ;; Match null string on the date-line so that the date-line
1057 ;; is grouped with what follows.
964141f2 1058 (set (make-local-variable 'page-delimiter) "^\\<\\|^\f")
dd309224 1059 (set (make-local-variable 'version-control) 'never)
7cd017ba
SM
1060 (set (make-local-variable 'smerge-resolve-function)
1061 'change-log-resolve-conflict)
dd309224 1062 (set (make-local-variable 'adaptive-fill-regexp) "\\s *")
4b286eca 1063 (set (make-local-variable 'font-lock-defaults)
73b27641 1064 '(change-log-font-lock-keywords t nil nil backward-paragraph))
e50fa43e 1065 (set (make-local-variable 'multi-isearch-next-buffer-function)
73b27641 1066 'change-log-next-buffer)
eb9c16e5 1067 (set (make-local-variable 'beginning-of-defun-function)
0bde6a03 1068 'change-log-beginning-of-defun)
eb9c16e5 1069 (set (make-local-variable 'end-of-defun-function)
7be2b094
TZ
1070 'change-log-end-of-defun)
1071 ;; next-error function glue
1072 (setq next-error-function 'change-log-next-error)
1073 (setq next-error-last-buffer (current-buffer)))
73b27641
JL
1074
1075(defun change-log-next-buffer (&optional buffer wrap)
1076 "Return the next buffer in the series of ChangeLog file buffers.
1077This function is used for multiple buffers isearch.
1078A sequence of buffers is formed by ChangeLog files with decreasing
1079numeric file name suffixes in the directory of the initial ChangeLog
1080file were isearch was started."
1081 (let* ((name (change-log-name))
1082 (files (cons name (sort (file-expand-wildcards
1083 (concat name "[-.][0-9]*"))
1084 (lambda (a b)
bac2f6bc
SM
1085 ;; The file's extension may not have a valid
1086 ;; version form (e.g. VC backup revisions).
1087 (ignore-errors
1088 (version< (substring b (length name))
1089 (substring a (length name))))))))
73b27641
JL
1090 (files (if isearch-forward files (reverse files))))
1091 (find-file-noselect
1092 (if wrap
1093 (car files)
1094 (cadr (member (file-name-nondirectory (buffer-file-name buffer))
1095 files))))))
21d7e080 1096
905b7d38
SM
1097(defun change-log-fill-forward-paragraph (n)
1098 "Cut paragraphs so filling preserves open parentheses at beginning of lines."
1099 (let (;; Add lines starting with whitespace followed by a left paren or an
6dbf6147 1100 ;; asterisk.
905b7d38
SM
1101 (paragraph-start (concat paragraph-start "\\|\\s *\\(?:\\s(\\|\\*\\)")))
1102 (forward-paragraph n)))
287d149f 1103\f
fcad5199 1104(defcustom add-log-current-defun-header-regexp
a8d693d8 1105 "^\\([[:upper:]][[:upper:]_ ]*[[:upper:]_]\\|[-_[:alpha:]]+\\)[ \t]*[:=]"
6d00ce04
CY
1106 "Heuristic regexp used by `add-log-current-defun' for unknown major modes.
1107The regexp's first submatch is placed in the ChangeLog entry, in
1108parentheses."
fcad5199
RS
1109 :type 'regexp
1110 :group 'change-log)
21d7e080 1111
fb644f48
EN
1112;;;###autoload
1113(defvar add-log-lisp-like-modes
d1921057 1114 '(emacs-lisp-mode lisp-mode scheme-mode dsssl-mode lisp-interaction-mode)
fb644f48
EN
1115 "*Modes that look like Lisp to `add-log-current-defun'.")
1116
1117;;;###autoload
1118(defvar add-log-c-like-modes
d1921057 1119 '(c-mode c++-mode c++-c-mode objc-mode)
fb644f48
EN
1120 "*Modes that look like C to `add-log-current-defun'.")
1121
1122;;;###autoload
1123(defvar add-log-tex-like-modes
bb042dc6 1124 '(TeX-mode plain-TeX-mode LaTeX-mode tex-mode)
fb644f48
EN
1125 "*Modes that look like TeX to `add-log-current-defun'.")
1126
ab319633
GM
1127(declare-function c-cpp-define-name "cc-cmds" ())
1128(declare-function c-defun-name "cc-cmds" ())
004a00f4 1129
e332f80b 1130;;;###autoload
21d7e080
ER
1131(defun add-log-current-defun ()
1132 "Return name of function definition point is in, or nil.
1133
63314951 1134Understands C, Lisp, LaTeX (\"functions\" are chapters, sections, ...),
5960adc7 1135Texinfo (@node titles) and Perl.
21d7e080
ER
1136
1137Other modes are handled by a heuristic that looks in the 10K before
1138point for uppercase headings starting in the first column or
5960adc7 1139identifiers followed by `:' or `='. See variables
c1356086 1140`add-log-current-defun-header-regexp' and
5ef08021 1141`add-log-current-defun-function'.
21d7e080
ER
1142
1143Has a preference of looking backwards."
2cc0b765
RS
1144 (condition-case nil
1145 (save-excursion
1146 (let ((location (point)))
5960adc7
DL
1147 (cond (add-log-current-defun-function
1148 (funcall add-log-current-defun-function))
bb042dc6 1149 ((apply 'derived-mode-p add-log-lisp-like-modes)
a0151877 1150 ;; If we are now precisely at the beginning of a defun,
2cc0b765
RS
1151 ;; make sure beginning-of-defun finds that one
1152 ;; rather than the previous one.
1153 (or (eobp) (forward-char 1))
1154 (beginning-of-defun)
5960adc7
DL
1155 ;; Make sure we are really inside the defun found,
1156 ;; not after it.
42b1fc29
RS
1157 (when (and (looking-at "\\s(")
1158 (progn (end-of-defun)
1159 (< location (point)))
1160 (progn (forward-sexp -1)
1161 (>= location (point))))
1162 (if (looking-at "\\s(")
1163 (forward-char 1))
1164 ;; Skip the defining construct name, typically "defun"
1165 ;; or "defvar".
1166 (forward-sexp 1)
1167 ;; The second element is usually a symbol being defined.
1168 ;; If it is not, use the first symbol in it.
63c7727f 1169 (skip-chars-forward " \t\n'(")
5960adc7
DL
1170 (buffer-substring-no-properties (point)
1171 (progn (forward-sexp 1)
1172 (point)))))
bb042dc6 1173 ((apply 'derived-mode-p add-log-c-like-modes)
221fcdaa
AM
1174 (or (c-cpp-define-name)
1175 (c-defun-name)))
1176 ((memq major-mode add-log-tex-like-modes)
2cc0b765 1177 (if (re-search-backward
5960adc7
DL
1178 "\\\\\\(sub\\)*\\(section\\|paragraph\\|chapter\\)"
1179 nil t)
2cc0b765
RS
1180 (progn
1181 (goto-char (match-beginning 0))
5960adc7
DL
1182 (buffer-substring-no-properties
1183 (1+ (point)) ; without initial backslash
1184 (line-end-position)))))
bb042dc6 1185 ((derived-mode-p 'texinfo-mode)
3071ee28 1186 (if (re-search-backward "^@node[ \t]+\\([^,\n]+\\)" nil t)
5960adc7 1187 (match-string-no-properties 1)))
fb1b68a4 1188 ((derived-mode-p 'perl-mode 'cperl-mode)
5a9ac14b 1189 (if (re-search-backward "^sub[ \t]+\\([^({ \t\n]+\\)" nil t)
5960adc7
DL
1190 (match-string-no-properties 1)))
1191 ;; Emacs's autoconf-mode installs its own
1192 ;; `add-log-current-defun-function'. This applies to
1193 ;; a different mode apparently for editing .m4
1194 ;; autoconf source.
bb042dc6 1195 ((derived-mode-p 'autoconf-mode)
5960adc7
DL
1196 (if (re-search-backward
1197 "^\\(\\(m4_\\)?define\\|A._DEFUN\\)(\\[?\\([A-Za-z0-9_]+\\)" nil t)
1198 (match-string-no-properties 3)))
2cc0b765
RS
1199 (t
1200 ;; If all else fails, try heuristics
c1356086
GM
1201 (let (case-fold-search
1202 result)
2cc0b765 1203 (end-of-line)
c1356086
GM
1204 (when (re-search-backward
1205 add-log-current-defun-header-regexp
1206 (- (point) 10000)
1207 t)
5960adc7
DL
1208 (setq result (or (match-string-no-properties 1)
1209 (match-string-no-properties 0)))
c1356086
GM
1210 ;; Strip whitespace away
1211 (when (string-match "\\([^ \t\n\r\f].*[^ \t\n\r\f]\\)"
1212 result)
5960adc7 1213 (setq result (match-string-no-properties 1 result)))
c1356086 1214 result))))))
2cc0b765 1215 (error nil)))
ef15f270 1216
83afd62c 1217(defvar change-log-get-method-definition-md)
15319a8f 1218
83afd62c 1219;; Subroutine used within change-log-get-method-definition.
59c1a7de
RS
1220;; Add the last match in the buffer to the end of `md',
1221;; followed by the string END; move to the end of that match.
83afd62c
KH
1222(defun change-log-get-method-definition-1 (end)
1223 (setq change-log-get-method-definition-md
1224 (concat change-log-get-method-definition-md
5960adc7 1225 (match-string 1)
15319a8f 1226 end))
59c1a7de
RS
1227 (goto-char (match-end 0)))
1228
83afd62c 1229(defun change-log-get-method-definition ()
eba72fc1 1230"For Objective C, return the method name if we are in a method."
83afd62c 1231 (let ((change-log-get-method-definition-md "["))
59c1a7de 1232 (save-excursion
f27f16ed 1233 (if (re-search-backward "^@implementation\\s-*\\([A-Za-z_]*\\)" nil t)
83afd62c 1234 (change-log-get-method-definition-1 " ")))
59c1a7de
RS
1235 (save-excursion
1236 (cond
f27f16ed 1237 ((re-search-forward "^\\([-+]\\)[ \t\n\f\r]*\\(([^)]*)\\)?\\s-*" nil t)
83afd62c 1238 (change-log-get-method-definition-1 "")
59c1a7de
RS
1239 (while (not (looking-at "[{;]"))
1240 (looking-at
f27f16ed 1241 "\\([A-Za-z_]*:?\\)\\s-*\\(([^)]*)\\)?[A-Za-z_]*[ \t\n\f\r]*")
83afd62c
KH
1242 (change-log-get-method-definition-1 ""))
1243 (concat change-log-get-method-definition-md "]"))))))
075a6629
DL
1244\f
1245(defun change-log-sortable-date-at ()
1246 "Return date of log entry in a consistent form for sorting.
1247Point is assumed to be at the start of the entry."
1248 (require 'timezone)
0bde6a03 1249 (if (looking-at change-log-start-entry-re)
075a6629
DL
1250 (let ((date (match-string-no-properties 0)))
1251 (if date
1252 (if (string-match "\\(....\\)-\\(..\\)-\\(..\\)\\s-+" date)
1253 (concat (match-string 1 date) (match-string 2 date)
1254 (match-string 3 date))
1255 (condition-case nil
1256 (timezone-make-date-sortable date)
1257 (error nil)))))
1258 (error "Bad date")))
59c1a7de 1259
7cd017ba
SM
1260(defun change-log-resolve-conflict ()
1261 "Function to be used in `smerge-resolve-function'."
74dea9e1
SM
1262 (save-excursion
1263 (save-restriction
1264 (narrow-to-region (match-beginning 0) (match-end 0))
1265 (let ((mb1 (match-beginning 1))
1266 (me1 (match-end 1))
1267 (mb3 (match-beginning 3))
1268 (me3 (match-end 3))
1269 (tmp1 (generate-new-buffer " *changelog-resolve-1*"))
1270 (tmp2 (generate-new-buffer " *changelog-resolve-2*")))
1271 (unwind-protect
1272 (let ((buf (current-buffer)))
1273 (with-current-buffer tmp1
1274 (change-log-mode)
1275 (insert-buffer-substring buf mb1 me1))
1276 (with-current-buffer tmp2
1277 (change-log-mode)
1278 (insert-buffer-substring buf mb3 me3)
1279 ;; Do the merge here instead of inside `buf' so as to be
1280 ;; more robust in case change-log-merge fails.
1281 (change-log-merge tmp1))
1282 (goto-char (point-max))
1283 (delete-region (point-min)
1284 (prog1 (point)
1285 (insert-buffer-substring tmp2))))
1286 (kill-buffer tmp1)
1287 (kill-buffer tmp2))))))
7cd017ba 1288
075a6629
DL
1289;;;###autoload
1290(defun change-log-merge (other-log)
eba72fc1 1291 "Merge the contents of change log file OTHER-LOG with this buffer.
075a6629 1292Both must be found in Change Log mode (since the merging depends on
7cd017ba
SM
1293the appropriate motion commands). OTHER-LOG can be either a file name
1294or a buffer.
075a6629 1295
918f4ac3
DL
1296Entries are inserted in chronological order. Both the current and
1297old-style time formats for entries are supported."
075a6629 1298 (interactive "*fLog file name to merge: ")
bb042dc6 1299 (if (not (derived-mode-p 'change-log-mode))
075a6629 1300 (error "Not in Change Log mode"))
7cd017ba
SM
1301 (let ((other-buf (if (bufferp other-log) other-log
1302 (find-file-noselect other-log)))
075a6629
DL
1303 (buf (current-buffer))
1304 date1 start end)
1305 (save-excursion
1306 (goto-char (point-min))
1307 (set-buffer other-buf)
1308 (goto-char (point-min))
bb042dc6 1309 (if (not (derived-mode-p 'change-log-mode))
075a6629
DL
1310 (error "%s not found in Change Log mode" other-log))
1311 ;; Loop through all the entries in OTHER-LOG.
1312 (while (not (eobp))
1313 (setq date1 (change-log-sortable-date-at))
1314 (setq start (point)
1315 end (progn (forward-page) (point)))
1316 ;; Look for an entry in original buffer that isn't later.
1317 (with-current-buffer buf
1318 (while (and (not (eobp))
1319 (string< date1 (change-log-sortable-date-at)))
1320 (forward-page))
1321 (if (not (eobp))
1322 (insert-buffer-substring other-buf start end)
1323 ;; At the end of the original buffer, insert a newline to
1324 ;; separate entries and then the rest of the file being
7cd017ba
SM
1325 ;; merged.
1326 (unless (or (bobp)
1327 (and (= ?\n (char-before))
1328 (or (<= (1- (point)) (point-min))
1329 (= ?\n (char-before (1- (point)))))))
97f4e87c 1330 (insert (if use-hard-newlines hard-newline "\n")))
7cd017ba
SM
1331 ;; Move to the end of it to terminate outer loop.
1332 (with-current-buffer other-buf
1333 (goto-char (point-max)))
1334 (insert-buffer-substring other-buf start)))))))
ef15f270 1335
0bde6a03
DN
1336(defun change-log-beginning-of-defun ()
1337 (re-search-backward change-log-start-entry-re nil 'move))
1338
1339(defun change-log-end-of-defun ()
1340 ;; Look back and if there is no entry there it means we are before
1341 ;; the first ChangeLog entry, so go forward until finding one.
1342 (unless (save-excursion (re-search-backward change-log-start-entry-re nil t))
1343 (re-search-forward change-log-start-entry-re nil t))
1344
1345 ;; In case we are at the end of log entry going forward a line will
1346 ;; make us find the next entry when searching. If we are inside of
1347 ;; an entry going forward a line will still keep the point inside
1348 ;; the same entry.
1349 (forward-line 1)
1350
1351 ;; In case we are at the beginning of an entry, move past it.
1352 (when (looking-at change-log-start-entry-re)
1353 (goto-char (match-end 0))
1354 (forward-line 1))
1355
1356 ;; Search for the start of the next log entry. Go to the end of the
1357 ;; buffer if we could not find a next entry.
1358 (when (re-search-forward change-log-start-entry-re nil 'move)
1359 (goto-char (match-beginning 0))
1360 (forward-line -1)))
1361
1da56800
RS
1362(provide 'add-log)
1363
d1921057 1364;; arch-tag: 81eee6fc-088f-4372-a37f-80ad9620e762
fd7fa35a 1365;;; add-log.el ends here