* x-dnd.el (x-dnd-maybe-call-test-function):
[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,
c4f6e489
GM
4;; 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009
5;; Free Software Foundation, Inc.
84fc2cfa 6
6228c05b 7;; Maintainer: FSF
df63ae66 8;; Keywords: 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.
248 ("^[0-9-]+ +\\|^\\(Sun\\|Mon\\|Tue\\|Wed\\|Thu\\|Fri\\|Sat\\) [A-z][a-z][a-z] [0-9:+ ]+"
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
SM
809 (unless (equal file-name buffer-file-name)
810 (if (or other-window (window-dedicated-p (selected-window)))
811 (find-file-other-window file-name)
812 (find-file file-name)))
bb042dc6 813 (or (derived-mode-p 'change-log-mode)
675a998f 814 (change-log-mode))
84fc2cfa
ER
815 (undo-boundary)
816 (goto-char (point-min))
d882f144 817
ad4573c7
SM
818 (let ((full-name (or add-log-full-name (user-full-name)))
819 (mailing-address (or add-log-mailing-address user-mail-address)))
820
821 (when whoami
1e899515 822 (setq full-name (read-string "Full name: " full-name))
ad4573c7
SM
823 ;; Note that some sites have room and phone number fields in
824 ;; full name which look silly when inserted. Rather than do
825 ;; anything about that here, let user give prefix argument so that
826 ;; s/he can edit the full name field in prompter if s/he wants.
827 (setq mailing-address
1e899515 828 (read-string "Mailing address: " mailing-address)))
ad4573c7
SM
829
830 ;; If file starts with a copyright and permission notice, skip them.
831 ;; Assume they end at first blank line.
832 (when (looking-at "Copyright")
833 (search-forward "\n\n")
834 (skip-chars-forward "\n"))
835
836 ;; Advance into first entry if it is usable; else make new one.
837 (let ((new-entries
838 (mapcar (lambda (addr)
839 (concat
840 (if (stringp add-log-time-zone-rule)
841 (let ((tz (getenv "TZ")))
842 (unwind-protect
843 (progn
844 (set-time-zone-rule add-log-time-zone-rule)
845 (funcall add-log-time-format))
846 (set-time-zone-rule tz)))
847 (funcall add-log-time-format))
848 " " full-name
849 " <" addr ">"))
850 (if (consp mailing-address)
851 mailing-address
852 (list mailing-address)))))
853 (if (and (not add-log-always-start-new-record)
854 (let ((hit nil))
855 (dolist (entry new-entries hit)
856 (when (looking-at (regexp-quote entry))
857 (setq hit t)))))
858 (forward-line 1)
859 (insert (nth (random (length new-entries))
860 new-entries)
861 (if use-hard-newlines hard-newline "\n")
862 (if use-hard-newlines hard-newline "\n"))
863 (forward-line -1))))
82f4acaf 864
d882f144
RS
865 ;; Determine where we should stop searching for a usable
866 ;; item to add to, within this entry.
ad4573c7
SM
867 (let ((bound
868 (save-excursion
869 (if (looking-at "\n*[^\n* \t]")
870 (skip-chars-forward "\n")
871 (if add-log-keep-changes-together
872 (forward-page) ; page delimits entries for date
873 (forward-paragraph))) ; paragraph delimits entries for file
874 (point))))
875
876 ;; Now insert the new line for this item.
877 (cond ((re-search-forward "^\\s *\\*\\s *$" bound t)
878 ;; Put this file name into the existing empty item.
879 (if item
880 (insert item)))
881 ((and (not new-entry)
882 (let (case-fold-search)
883 (re-search-forward
884 (concat (regexp-quote (concat "* " item))
885 ;; Don't accept `foo.bar' when
886 ;; looking for `foo':
887 "\\(\\s \\|[(),:]\\)")
888 bound t)))
889 ;; Add to the existing item for the same file.
890 (re-search-forward "^\\s *$\\|^\\s \\*")
891 (goto-char (match-beginning 0))
892 ;; Delete excess empty lines; make just 2.
893 (while (and (not (eobp)) (looking-at "^\\s *$"))
894 (delete-region (point) (line-beginning-position 2)))
895 (insert (if use-hard-newlines hard-newline "\n")
896 (if use-hard-newlines hard-newline "\n"))
897 (forward-line -2)
898 (indent-relative-maybe))
899 (t
900 ;; Make a new item.
901 (while (looking-at "\\sW")
902 (forward-line 1))
903 (while (and (not (eobp)) (looking-at "^\\s *$"))
904 (delete-region (point) (line-beginning-position 2)))
905 (insert (if use-hard-newlines hard-newline "\n")
906 (if use-hard-newlines hard-newline "\n")
907 (if use-hard-newlines hard-newline "\n"))
908 (forward-line -2)
909 (indent-to left-margin)
910 (insert "* ")
911 (if item (insert item)))))
1832dbd1 912 ;; Now insert the function name, if we have one.
d882f144 913 ;; Point is at the item for this file,
21d7e080 914 ;; either at the end of the line or at the first blank line.
5a9ac14b
SM
915 (if (not defun)
916 ;; No function name, so put in a colon unless we have just a star.
917 (unless (save-excursion
918 (beginning-of-line 1)
919 (looking-at "\\s *\\(\\*\\s *\\)?$"))
920 (insert ": ")
7b297602 921 (if version (insert version ?\s)))
5a9ac14b
SM
922 ;; Make it easy to get rid of the function name.
923 (undo-boundary)
5960adc7
DL
924 (unless (save-excursion
925 (beginning-of-line 1)
5a9ac14b 926 (looking-at "\\s *$"))
7b297602 927 (insert ?\s))
5a9ac14b
SM
928 ;; See if the prev function name has a message yet or not.
929 ;; If not, merge the two items.
930 (let ((pos (point-marker)))
931 (skip-syntax-backward " ")
932 (skip-chars-backward "):")
2a520399
DN
933 (if (and (not put-new-entry-on-new-line)
934 (looking-at "):")
fc9b0554
SM
935 (let ((pos (save-excursion (backward-sexp 1) (point))))
936 (when (equal (buffer-substring pos (point)) defun)
937 (delete-region pos (point)))
938 (> fill-column (+ (current-column) (length defun) 4))))
939 (progn (skip-chars-backward ", ")
940 (delete-region (point) pos)
941 (unless (memq (char-before) '(?\()) (insert ", ")))
2a520399
DN
942 (when (and (not put-new-entry-on-new-line) (looking-at "):"))
943 (delete-region (+ 1 (point)) (line-end-position)))
5a9ac14b
SM
944 (goto-char pos)
945 (insert "("))
946 (set-marker pos nil))
947 (insert defun "): ")
7b297602 948 (if version (insert version ?\s)))))
84fc2cfa 949
84fc2cfa
ER
950;;;###autoload
951(defun add-change-log-entry-other-window (&optional whoami file-name)
d882f144
RS
952 "Find change log file in other window and add entry and item.
953This is just like `add-change-log-entry' except that it displays
954the change log file in another window."
84fc2cfa
ER
955 (interactive (if current-prefix-arg
956 (list current-prefix-arg
957 (prompt-for-change-log-name))))
958 (add-change-log-entry whoami file-name t))
959
6dbf6147 960
d1921057 961(defvar change-log-indent-text 0)
fc9b0554 962
6dbf6147
MR
963(defun change-log-fill-parenthesized-list ()
964 ;; Fill parenthesized lists of names according to GNU standards.
965 ;; * file-name.ext (very-long-foo, very-long-bar, very-long-foobar):
966 ;; should be filled as
967 ;; * file-name.ext (very-long-foo, very-long-bar)
968 ;; (very-long-foobar):
969 (save-excursion
970 (end-of-line 0)
971 (skip-chars-backward " \t")
972 (when (and (equal (char-before) ?\,)
973 (> (point) (1+ (point-min))))
974 (condition-case nil
975 (when (save-excursion
976 (and (prog2
977 (up-list -1)
978 (equal (char-after) ?\()
979 (skip-chars-backward " \t"))
980 (or (bolp)
981 ;; Skip everything but a whitespace or asterisk.
982 (and (not (zerop (skip-chars-backward "^ \t\n*")))
983 (skip-chars-backward " \t")
984 ;; We want one asterisk here.
985 (= (skip-chars-backward "*") -1)
986 (skip-chars-backward " \t")
987 (bolp)))))
988 ;; Delete the comma.
989 (delete-char -1)
990 ;; Close list on previous line.
991 (insert ")")
992 (skip-chars-forward " \t\n")
993 ;; Start list on new line.
994 (insert-before-markers "("))
995 (error nil)))))
996
d1921057 997(defun change-log-indent ()
6dbf6147 998 (change-log-fill-parenthesized-list)
fc9b0554
SM
999 (let* ((indent
1000 (save-excursion
1001 (beginning-of-line)
1002 (skip-chars-forward " \t")
1003 (cond
d1921057 1004 ((and (looking-at "\\(.*\\) [^ \n].*[^ \n] <.*>\\(?: +(.*)\\)? *$")
fc9b0554
SM
1005 ;; Matching the output of add-log-time-format is difficult,
1006 ;; but I'll get it has at least two adjacent digits.
1007 (string-match "[[:digit:]][[:digit:]]" (match-string 1)))
1008 0)
1009 ((looking-at "[^*(]")
d1921057 1010 (+ (current-left-margin) change-log-indent-text))
fc9b0554
SM
1011 (t (current-left-margin)))))
1012 (pos (save-excursion (indent-line-to indent) (point))))
1013 (if (> pos (point)) (goto-char pos))))
1014
1015
3066d4ad 1016(defvar smerge-resolve-function)
8bb4ed88 1017(defvar copyright-at-end-flag)
3066d4ad 1018
1da56800 1019;;;###autoload
7cd017ba 1020(define-derived-mode change-log-mode text-mode "Change Log"
eb9c16e5 1021 "Major mode for editing change logs; like Indented Text mode.
09b389d0 1022Prevents numeric backups and sets `left-margin' to 8 and `fill-column' to 74.
3697b807 1023New log entries are usually made with \\[add-change-log-entry] or \\[add-change-log-entry-other-window].
5516387c 1024Each entry behaves as a paragraph, and the entries for one day as a page.
3066d4ad 1025Runs `change-log-mode-hook'.
eb9c16e5 1026\n\\{change-log-mode-map}"
7cd017ba 1027 (setq left-margin 8
4f675a8c 1028 fill-column 74
60f10a06 1029 indent-tabs-mode t
ccf0d2ca
JB
1030 tab-width 8
1031 show-trailing-whitespace t)
905b7d38
SM
1032 (set (make-local-variable 'fill-forward-paragraph-function)
1033 'change-log-fill-forward-paragraph)
1034 ;; Make sure we call `change-log-indent' when filling.
1035 (set (make-local-variable 'fill-indent-according-to-mode) t)
3ee9a09c
MR
1036 ;; Avoid that filling leaves behind a single "*" on a line.
1037 (add-hook 'fill-nobreak-predicate
1038 '(lambda ()
8bb4ed88 1039 (looking-back "^\\s *\\*\\s *" (line-beginning-position)))
3ee9a09c 1040 nil t)
d1921057 1041 (set (make-local-variable 'indent-line-function) 'change-log-indent)
fc9b0554 1042 (set (make-local-variable 'tab-always-indent) nil)
8bb4ed88 1043 (set (make-local-variable 'copyright-at-end-flag) t)
4b7d4d0d
DL
1044 ;; We really do want "^" in paragraph-start below: it is only the
1045 ;; lines that begin at column 0 (despite the left-margin of 8) that
1046 ;; we are looking for. Adding `* ' allows eliding the blank line
1047 ;; between entries for different files.
c9cfb0f2 1048 (set (make-local-variable 'paragraph-start) "\\s *$\\|\f\\|^\\<")
4b7d4d0d 1049 (set (make-local-variable 'paragraph-separate) paragraph-start)
2c91c85c
RS
1050 ;; Match null string on the date-line so that the date-line
1051 ;; is grouped with what follows.
964141f2 1052 (set (make-local-variable 'page-delimiter) "^\\<\\|^\f")
dd309224 1053 (set (make-local-variable 'version-control) 'never)
7cd017ba
SM
1054 (set (make-local-variable 'smerge-resolve-function)
1055 'change-log-resolve-conflict)
dd309224 1056 (set (make-local-variable 'adaptive-fill-regexp) "\\s *")
4b286eca 1057 (set (make-local-variable 'font-lock-defaults)
73b27641 1058 '(change-log-font-lock-keywords t nil nil backward-paragraph))
e50fa43e 1059 (set (make-local-variable 'multi-isearch-next-buffer-function)
73b27641 1060 'change-log-next-buffer)
eb9c16e5 1061 (set (make-local-variable 'beginning-of-defun-function)
0bde6a03 1062 'change-log-beginning-of-defun)
eb9c16e5 1063 (set (make-local-variable 'end-of-defun-function)
7be2b094
TZ
1064 'change-log-end-of-defun)
1065 ;; next-error function glue
1066 (setq next-error-function 'change-log-next-error)
1067 (setq next-error-last-buffer (current-buffer)))
73b27641
JL
1068
1069(defun change-log-next-buffer (&optional buffer wrap)
1070 "Return the next buffer in the series of ChangeLog file buffers.
1071This function is used for multiple buffers isearch.
1072A sequence of buffers is formed by ChangeLog files with decreasing
1073numeric file name suffixes in the directory of the initial ChangeLog
1074file were isearch was started."
1075 (let* ((name (change-log-name))
1076 (files (cons name (sort (file-expand-wildcards
1077 (concat name "[-.][0-9]*"))
1078 (lambda (a b)
bac2f6bc
SM
1079 ;; The file's extension may not have a valid
1080 ;; version form (e.g. VC backup revisions).
1081 (ignore-errors
1082 (version< (substring b (length name))
1083 (substring a (length name))))))))
73b27641
JL
1084 (files (if isearch-forward files (reverse files))))
1085 (find-file-noselect
1086 (if wrap
1087 (car files)
1088 (cadr (member (file-name-nondirectory (buffer-file-name buffer))
1089 files))))))
21d7e080 1090
905b7d38
SM
1091(defun change-log-fill-forward-paragraph (n)
1092 "Cut paragraphs so filling preserves open parentheses at beginning of lines."
1093 (let (;; Add lines starting with whitespace followed by a left paren or an
6dbf6147 1094 ;; asterisk.
905b7d38
SM
1095 (paragraph-start (concat paragraph-start "\\|\\s *\\(?:\\s(\\|\\*\\)")))
1096 (forward-paragraph n)))
287d149f 1097\f
fcad5199 1098(defcustom add-log-current-defun-header-regexp
a8d693d8 1099 "^\\([[:upper:]][[:upper:]_ ]*[[:upper:]_]\\|[-_[:alpha:]]+\\)[ \t]*[:=]"
6d00ce04
CY
1100 "Heuristic regexp used by `add-log-current-defun' for unknown major modes.
1101The regexp's first submatch is placed in the ChangeLog entry, in
1102parentheses."
fcad5199
RS
1103 :type 'regexp
1104 :group 'change-log)
21d7e080 1105
fb644f48
EN
1106;;;###autoload
1107(defvar add-log-lisp-like-modes
d1921057 1108 '(emacs-lisp-mode lisp-mode scheme-mode dsssl-mode lisp-interaction-mode)
fb644f48
EN
1109 "*Modes that look like Lisp to `add-log-current-defun'.")
1110
1111;;;###autoload
1112(defvar add-log-c-like-modes
d1921057 1113 '(c-mode c++-mode c++-c-mode objc-mode)
fb644f48
EN
1114 "*Modes that look like C to `add-log-current-defun'.")
1115
1116;;;###autoload
1117(defvar add-log-tex-like-modes
bb042dc6 1118 '(TeX-mode plain-TeX-mode LaTeX-mode tex-mode)
fb644f48
EN
1119 "*Modes that look like TeX to `add-log-current-defun'.")
1120
ab319633
GM
1121(declare-function c-cpp-define-name "cc-cmds" ())
1122(declare-function c-defun-name "cc-cmds" ())
004a00f4 1123
e332f80b 1124;;;###autoload
21d7e080
ER
1125(defun add-log-current-defun ()
1126 "Return name of function definition point is in, or nil.
1127
63314951 1128Understands C, Lisp, LaTeX (\"functions\" are chapters, sections, ...),
5960adc7 1129Texinfo (@node titles) and Perl.
21d7e080
ER
1130
1131Other modes are handled by a heuristic that looks in the 10K before
1132point for uppercase headings starting in the first column or
5960adc7 1133identifiers followed by `:' or `='. See variables
c1356086 1134`add-log-current-defun-header-regexp' and
5ef08021 1135`add-log-current-defun-function'.
21d7e080
ER
1136
1137Has a preference of looking backwards."
2cc0b765
RS
1138 (condition-case nil
1139 (save-excursion
1140 (let ((location (point)))
5960adc7
DL
1141 (cond (add-log-current-defun-function
1142 (funcall add-log-current-defun-function))
bb042dc6 1143 ((apply 'derived-mode-p add-log-lisp-like-modes)
a0151877 1144 ;; If we are now precisely at the beginning of a defun,
2cc0b765
RS
1145 ;; make sure beginning-of-defun finds that one
1146 ;; rather than the previous one.
1147 (or (eobp) (forward-char 1))
1148 (beginning-of-defun)
5960adc7
DL
1149 ;; Make sure we are really inside the defun found,
1150 ;; not after it.
42b1fc29
RS
1151 (when (and (looking-at "\\s(")
1152 (progn (end-of-defun)
1153 (< location (point)))
1154 (progn (forward-sexp -1)
1155 (>= location (point))))
1156 (if (looking-at "\\s(")
1157 (forward-char 1))
1158 ;; Skip the defining construct name, typically "defun"
1159 ;; or "defvar".
1160 (forward-sexp 1)
1161 ;; The second element is usually a symbol being defined.
1162 ;; If it is not, use the first symbol in it.
63c7727f 1163 (skip-chars-forward " \t\n'(")
5960adc7
DL
1164 (buffer-substring-no-properties (point)
1165 (progn (forward-sexp 1)
1166 (point)))))
bb042dc6 1167 ((apply 'derived-mode-p add-log-c-like-modes)
221fcdaa
AM
1168 (or (c-cpp-define-name)
1169 (c-defun-name)))
1170 ((memq major-mode add-log-tex-like-modes)
2cc0b765 1171 (if (re-search-backward
5960adc7
DL
1172 "\\\\\\(sub\\)*\\(section\\|paragraph\\|chapter\\)"
1173 nil t)
2cc0b765
RS
1174 (progn
1175 (goto-char (match-beginning 0))
5960adc7
DL
1176 (buffer-substring-no-properties
1177 (1+ (point)) ; without initial backslash
1178 (line-end-position)))))
bb042dc6 1179 ((derived-mode-p 'texinfo-mode)
3071ee28 1180 (if (re-search-backward "^@node[ \t]+\\([^,\n]+\\)" nil t)
5960adc7 1181 (match-string-no-properties 1)))
fb1b68a4 1182 ((derived-mode-p 'perl-mode 'cperl-mode)
5a9ac14b 1183 (if (re-search-backward "^sub[ \t]+\\([^({ \t\n]+\\)" nil t)
5960adc7
DL
1184 (match-string-no-properties 1)))
1185 ;; Emacs's autoconf-mode installs its own
1186 ;; `add-log-current-defun-function'. This applies to
1187 ;; a different mode apparently for editing .m4
1188 ;; autoconf source.
bb042dc6 1189 ((derived-mode-p 'autoconf-mode)
5960adc7
DL
1190 (if (re-search-backward
1191 "^\\(\\(m4_\\)?define\\|A._DEFUN\\)(\\[?\\([A-Za-z0-9_]+\\)" nil t)
1192 (match-string-no-properties 3)))
2cc0b765
RS
1193 (t
1194 ;; If all else fails, try heuristics
c1356086
GM
1195 (let (case-fold-search
1196 result)
2cc0b765 1197 (end-of-line)
c1356086
GM
1198 (when (re-search-backward
1199 add-log-current-defun-header-regexp
1200 (- (point) 10000)
1201 t)
5960adc7
DL
1202 (setq result (or (match-string-no-properties 1)
1203 (match-string-no-properties 0)))
c1356086
GM
1204 ;; Strip whitespace away
1205 (when (string-match "\\([^ \t\n\r\f].*[^ \t\n\r\f]\\)"
1206 result)
5960adc7 1207 (setq result (match-string-no-properties 1 result)))
c1356086 1208 result))))))
2cc0b765 1209 (error nil)))
ef15f270 1210
83afd62c 1211(defvar change-log-get-method-definition-md)
15319a8f 1212
83afd62c 1213;; Subroutine used within change-log-get-method-definition.
59c1a7de
RS
1214;; Add the last match in the buffer to the end of `md',
1215;; followed by the string END; move to the end of that match.
83afd62c
KH
1216(defun change-log-get-method-definition-1 (end)
1217 (setq change-log-get-method-definition-md
1218 (concat change-log-get-method-definition-md
5960adc7 1219 (match-string 1)
15319a8f 1220 end))
59c1a7de
RS
1221 (goto-char (match-end 0)))
1222
83afd62c 1223(defun change-log-get-method-definition ()
eba72fc1 1224"For Objective C, return the method name if we are in a method."
83afd62c 1225 (let ((change-log-get-method-definition-md "["))
59c1a7de 1226 (save-excursion
f27f16ed 1227 (if (re-search-backward "^@implementation\\s-*\\([A-Za-z_]*\\)" nil t)
83afd62c 1228 (change-log-get-method-definition-1 " ")))
59c1a7de
RS
1229 (save-excursion
1230 (cond
f27f16ed 1231 ((re-search-forward "^\\([-+]\\)[ \t\n\f\r]*\\(([^)]*)\\)?\\s-*" nil t)
83afd62c 1232 (change-log-get-method-definition-1 "")
59c1a7de
RS
1233 (while (not (looking-at "[{;]"))
1234 (looking-at
f27f16ed 1235 "\\([A-Za-z_]*:?\\)\\s-*\\(([^)]*)\\)?[A-Za-z_]*[ \t\n\f\r]*")
83afd62c
KH
1236 (change-log-get-method-definition-1 ""))
1237 (concat change-log-get-method-definition-md "]"))))))
075a6629
DL
1238\f
1239(defun change-log-sortable-date-at ()
1240 "Return date of log entry in a consistent form for sorting.
1241Point is assumed to be at the start of the entry."
1242 (require 'timezone)
0bde6a03 1243 (if (looking-at change-log-start-entry-re)
075a6629
DL
1244 (let ((date (match-string-no-properties 0)))
1245 (if date
1246 (if (string-match "\\(....\\)-\\(..\\)-\\(..\\)\\s-+" date)
1247 (concat (match-string 1 date) (match-string 2 date)
1248 (match-string 3 date))
1249 (condition-case nil
1250 (timezone-make-date-sortable date)
1251 (error nil)))))
1252 (error "Bad date")))
59c1a7de 1253
7cd017ba
SM
1254(defun change-log-resolve-conflict ()
1255 "Function to be used in `smerge-resolve-function'."
74dea9e1
SM
1256 (save-excursion
1257 (save-restriction
1258 (narrow-to-region (match-beginning 0) (match-end 0))
1259 (let ((mb1 (match-beginning 1))
1260 (me1 (match-end 1))
1261 (mb3 (match-beginning 3))
1262 (me3 (match-end 3))
1263 (tmp1 (generate-new-buffer " *changelog-resolve-1*"))
1264 (tmp2 (generate-new-buffer " *changelog-resolve-2*")))
1265 (unwind-protect
1266 (let ((buf (current-buffer)))
1267 (with-current-buffer tmp1
1268 (change-log-mode)
1269 (insert-buffer-substring buf mb1 me1))
1270 (with-current-buffer tmp2
1271 (change-log-mode)
1272 (insert-buffer-substring buf mb3 me3)
1273 ;; Do the merge here instead of inside `buf' so as to be
1274 ;; more robust in case change-log-merge fails.
1275 (change-log-merge tmp1))
1276 (goto-char (point-max))
1277 (delete-region (point-min)
1278 (prog1 (point)
1279 (insert-buffer-substring tmp2))))
1280 (kill-buffer tmp1)
1281 (kill-buffer tmp2))))))
7cd017ba 1282
075a6629
DL
1283;;;###autoload
1284(defun change-log-merge (other-log)
eba72fc1 1285 "Merge the contents of change log file OTHER-LOG with this buffer.
075a6629 1286Both must be found in Change Log mode (since the merging depends on
7cd017ba
SM
1287the appropriate motion commands). OTHER-LOG can be either a file name
1288or a buffer.
075a6629 1289
918f4ac3
DL
1290Entries are inserted in chronological order. Both the current and
1291old-style time formats for entries are supported."
075a6629 1292 (interactive "*fLog file name to merge: ")
bb042dc6 1293 (if (not (derived-mode-p 'change-log-mode))
075a6629 1294 (error "Not in Change Log mode"))
7cd017ba
SM
1295 (let ((other-buf (if (bufferp other-log) other-log
1296 (find-file-noselect other-log)))
075a6629
DL
1297 (buf (current-buffer))
1298 date1 start end)
1299 (save-excursion
1300 (goto-char (point-min))
1301 (set-buffer other-buf)
1302 (goto-char (point-min))
bb042dc6 1303 (if (not (derived-mode-p 'change-log-mode))
075a6629
DL
1304 (error "%s not found in Change Log mode" other-log))
1305 ;; Loop through all the entries in OTHER-LOG.
1306 (while (not (eobp))
1307 (setq date1 (change-log-sortable-date-at))
1308 (setq start (point)
1309 end (progn (forward-page) (point)))
1310 ;; Look for an entry in original buffer that isn't later.
1311 (with-current-buffer buf
1312 (while (and (not (eobp))
1313 (string< date1 (change-log-sortable-date-at)))
1314 (forward-page))
1315 (if (not (eobp))
1316 (insert-buffer-substring other-buf start end)
1317 ;; At the end of the original buffer, insert a newline to
1318 ;; separate entries and then the rest of the file being
7cd017ba
SM
1319 ;; merged.
1320 (unless (or (bobp)
1321 (and (= ?\n (char-before))
1322 (or (<= (1- (point)) (point-min))
1323 (= ?\n (char-before (1- (point)))))))
97f4e87c 1324 (insert (if use-hard-newlines hard-newline "\n")))
7cd017ba
SM
1325 ;; Move to the end of it to terminate outer loop.
1326 (with-current-buffer other-buf
1327 (goto-char (point-max)))
1328 (insert-buffer-substring other-buf start)))))))
ef15f270 1329
0bde6a03
DN
1330(defun change-log-beginning-of-defun ()
1331 (re-search-backward change-log-start-entry-re nil 'move))
1332
1333(defun change-log-end-of-defun ()
1334 ;; Look back and if there is no entry there it means we are before
1335 ;; the first ChangeLog entry, so go forward until finding one.
1336 (unless (save-excursion (re-search-backward change-log-start-entry-re nil t))
1337 (re-search-forward change-log-start-entry-re nil t))
1338
1339 ;; In case we are at the end of log entry going forward a line will
1340 ;; make us find the next entry when searching. If we are inside of
1341 ;; an entry going forward a line will still keep the point inside
1342 ;; the same entry.
1343 (forward-line 1)
1344
1345 ;; In case we are at the beginning of an entry, move past it.
1346 (when (looking-at change-log-start-entry-re)
1347 (goto-char (match-end 0))
1348 (forward-line 1))
1349
1350 ;; Search for the start of the next log entry. Go to the end of the
1351 ;; buffer if we could not find a next entry.
1352 (when (re-search-forward change-log-start-entry-re nil 'move)
1353 (goto-char (match-beginning 0))
1354 (forward-line -1)))
1355
1da56800
RS
1356(provide 'add-log)
1357
d1921057 1358;; arch-tag: 81eee6fc-088f-4372-a37f-80ad9620e762
fd7fa35a 1359;;; add-log.el ends here