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