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