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